From 9fd6445271b1baa10d9b2ebd32f48e8ac441694c Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sat, 10 Apr 2021 23:16:26 +0100
Subject: [PATCH 01/64] Fix view command, that was reloading tickers

---
 gamestonk_terminal/main_helper.py | 18 ------------------
 terminal.py                       |  3 ++-
 2 files changed, 2 insertions(+), 19 deletions(-)

diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 27c0cacdb090..aee3aa5d49e2 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -369,24 +369,6 @@ def view(l_args, s_ticker, s_start, s_interval, df_stock):
 
     type_candles = lett_to_num(ns_parser.type)
 
-    try:
-        ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas")
-        # Daily
-        if s_interval == "1440min":
-            # pylint: disable=unbalanced-tuple-unpacking
-            df_stock, _ = ts.get_daily_adjusted(symbol=s_ticker, outputsize="full")
-        # Intraday
-        else:
-            # pylint: disable=unbalanced-tuple-unpacking
-            df_stock, _ = ts.get_intraday(
-                symbol=s_ticker, outputsize="full", interval=s_interval
-            )
-
-    except Exception as e:
-        print(e)
-        print("Either the ticker or the API_KEY are invalids. Try again!")
-        return
-
     df_stock.sort_index(ascending=True, inplace=True)
 
     # Slice dataframe from the starting date YYYY-MM-DD selected
diff --git a/terminal.py b/terminal.py
index 74e02a04cd2a..7e8f5e7e9b9b 100644
--- a/terminal.py
+++ b/terminal.py
@@ -177,7 +177,8 @@ def main():
 
             else:
                 print(
-                    "No ticker selected. Use 'load ticker' to load the ticker you want to look at."
+                    "No ticker selected. Use 'load ticker' to load the ticker you want to look at.",
+                    "\n"
                 )
             main_cmd = True
 

From 9d4555dce417e6726c21faad6b5c23d8d7c27d59 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sat, 10 Apr 2021 23:18:25 +0100
Subject: [PATCH 02/64] Add note on how to load Indian tickers

---
 gamestonk_terminal/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 46e946a0161a..6cb5b572bb44 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -23,7 +23,7 @@ The main menu allows the following commands:
 ```
 load -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}]
 ```
-   * Load stock ticker to perform analysis on
+   * Load stock ticker to perform analysis on. To load an Indian ticker use `.NS` at the end, e.g. `SBIN.NS`.
      * -s : The starting date (format YYYY-MM-DD) of the stock
      * -i : Intraday stock minutes
 

From dd062f0e3958a5c2a7076955711b44464476bd11 Mon Sep 17 00:00:00 2001
From: DidierRLopes <dro.lopes@campus.fct.unl.pt>
Date: Sat, 10 Apr 2021 23:51:54 +0100
Subject: [PATCH 03/64] Add information about updating terminal

---
 README.md | 54 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 38 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md
index eb6c28d634af..702f62435f8b 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,9 @@
       <ul>
         <li><a href="#install">Install</a></li>
         <li><a href="#advanced-user-install---machine-learning">Advanced User Install - Machine Learning</a></li>
+        <li><a href="#other-issues">Other Issues</a></li>
+        <li><a href="#modulenotfounderror">ModuleNotFoundError</a></li>
+        <li><a href="#update-terminal">Update Terminal</a></li>
         <li><a href="#api-keys">API Keys</a></li>
         <li><a href="#usage">Usage</a></li>
       </ul>
@@ -120,6 +123,14 @@ Navigate into the folder with: `cd GamestonkTerminal/`
 conda install poetry
 ```
 
+5.5. If installing python 3.8
+```
+conda deactivate
+conda activate gst
+```
+*The `conda deactivate` -> `conda activate` in the middle is on purpose, this is sometimes required to avoid issues with poetry*
+
+
 6. Install poetry dependencies
 ```
 poetry install
@@ -132,21 +143,6 @@ This is a library for package management, and ensures a smoother experience than
 python terminal.py
 ```
 
-### Advanced User Install - Python 3.8
-
-*Note that the `conda deactivate` -> `conda activate` in the middle is on purpose, this is sometimes required to avoid issues with poetry*
-
-```
-conda create -n gst python=3.8.8
-conda activate gst
-conda install poetry
-conda deactivate
-conda activate gst
-poetry install
-conda install -c conda-forge fbprophet numpy=1.19.5 hdf5=1.10.5
-poetry install -E prediction
-```
-
 ### Advanced User Install - Machine Learning
 
 If you are an advanced user and use other Python distributions, we have several requirements.txt documents that you can pick from to download project dependencies.
@@ -185,7 +181,7 @@ Note: The problem with docker is that it won't output matplotlib figures.
 * `pip install pystan --upgrade`
 * `poetry update --lock`
 
-### Other issues
+### Other Issues
 
 If you run into trouble with poetry and the advice above did not help, your best bet is to try
 
@@ -215,6 +211,7 @@ If you run into trouble with poetry and the advice above did not help, your best
 7. Submit a ticket on github
 
 ### ModuleNotFoundError
+
 In the case when you run into an error of the form `ModuleNotFoundError: No module named '_______'`.  The solution is to
 install the missing package via pip.  
 
@@ -223,6 +220,31 @@ If you get the error that `statsmodels` is not found, you would run
 
 Then please submit an issue so that we can address why that was not imported.
 
+### Update Terminal
+
+The terminal is constantly being updated with new features and bug fixes, hence, for your terminal to be updaate, you can run:
+```
+git pull
+```
+to get the latest changes.
+
+If this fails due to the fact that you had modified some python files, and there's a conflict with the updates, you can use:
+```
+git stash
+```
+
+Then, re-run `poetry install` in order to install latest packages if there are new ones.
+```
+poetry install
+```
+
+Once installation is finished, you're ready to gamestonk.
+
+If you `stashed` your changes previously, you can un-stash them with:
+```
+git stash pop
+```
+
 ### API Keys
 
 The project is build around several different API calls, whether it is to access historical data or financials.

From 02c7d6c111ff7c8258d0c69104093fc7a671d5ce Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sat, 10 Apr 2021 23:54:08 +0100
Subject: [PATCH 04/64] black fix

---
 terminal.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/terminal.py b/terminal.py
index 7e8f5e7e9b9b..91fa02c2e94d 100644
--- a/terminal.py
+++ b/terminal.py
@@ -178,7 +178,7 @@ def main():
             else:
                 print(
                     "No ticker selected. Use 'load ticker' to load the ticker you want to look at.",
-                    "\n"
+                    "\n",
                 )
             main_cmd = True
 

From 09fa9dadb9cb3eb29fd13e4bee840af722a7b73b Mon Sep 17 00:00:00 2001
From: DidierRLopes <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 01:16:18 +0100
Subject: [PATCH 05/64] Update README.md

---
 README.md | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 702f62435f8b..a19102e31825 100644
--- a/README.md
+++ b/README.md
@@ -220,6 +220,8 @@ If you get the error that `statsmodels` is not found, you would run
 
 Then please submit an issue so that we can address why that was not imported.
 
+Please note that the package `pmdarima` needs to installed through `pip install` and not through `conda install`.
+
 ### Update Terminal
 
 The terminal is constantly being updated with new features and bug fixes, hence, for your terminal to be updaate, you can run:
@@ -233,10 +235,7 @@ If this fails due to the fact that you had modified some python files, and there
 git stash
 ```
 
-Then, re-run `poetry install` in order to install latest packages if there are new ones.
-```
-poetry install
-```
+Then, re-run `poetry install` or  `pip install -r requirements.txt` to get any new dependencies.
 
 Once installation is finished, you're ready to gamestonk.
 

From be586fce98b706d1af51222253ba243a2d8aade5 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 01:53:25 +0100
Subject: [PATCH 06/64] add intraday data with yahoo finance and pre/post
 market hours

---
 gamestonk_terminal/README.md      |  5 ++-
 gamestonk_terminal/main_helper.py | 64 ++++++++++++++++++++++++++++---
 2 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 6cb5b572bb44..95f9d7ce9057 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -23,9 +23,11 @@ The main menu allows the following commands:
 ```
 load -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}]
 ```
-   * Load stock ticker to perform analysis on. To load an Indian ticker use `.NS` at the end, e.g. `SBIN.NS`.
+   * Load stock ticker to perform analysis on. To load an Indian ticker use '.NS' at the end, e.g. 'SBIN.NS'
      * -s : The starting date (format YYYY-MM-DD) of the stock
      * -i : Intraday stock minutes
+     * --source : Source of historical data. 'yf' and 'av' available. Default 'yf'
+     * -p : Pre/After market hours. Only works for 'yf' source, and intraday data
 
 **Note:** Until a ticker is loaded, the menu will only show *disc* and *sen* menu, as the others require a ticker being provided.
 
@@ -33,6 +35,7 @@ load -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}]
 clear
 ```
    * Clear previously loaded stock ticker.
+
 ```
 view -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}] [--type N_TYPE]
 ```
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index aee3aa5d49e2..0a790667af31 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -5,6 +5,8 @@
 from alpha_vantage.timeseries import TimeSeries
 import mplfinance as mpf
 import yfinance as yf
+import pytz
+from datetime import datetime, timedelta
 
 from gamestonk_terminal.helper_funcs import (
     valid_date,
@@ -117,7 +119,7 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock):
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="load",
-        description=""" Load a stock in order to perform analysis.""",
+        description=""" Load a stock in order to perform analysis. To load an Indian ticker use '.NS' at the end, e.g. 'SBIN.NS'. """,
     )
     parser.add_argument(
         "-t",
@@ -151,8 +153,15 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock):
         dest="source",
         type=check_sources,
         default="yf",
-        help="Source of historical data. Intraday: Only 'av' available."
-        "Daily: 'yf' and 'av' available.",
+        help="Source of historical data. 'yf' and 'av' available.",
+    )
+    parser.add_argument(
+        "-p",
+        "--prepost",
+        action="store_true",
+        default=False,
+        dest="b_prepost",
+        help="Pre/After market hours. Only works for 'yf' source, and intraday data",
     )
 
     try:
@@ -229,9 +238,54 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock):
                 df_stock = df_stock[ns_parser.s_start_date :]
 
             elif ns_parser.source == "yf":
-                print(
-                    "Unfortunately, yahoo finance historical data doesn't contain intraday prices"
+                s_int = s_interval[:-2]
+
+                d_granularity = {"1m": 6, "5m": 59, "15m": 59, "30m": 59, "60m": 729}
+
+                s_start_dt = datetime.utcnow() - timedelta(days=d_granularity[s_int])
+                s_date_start = s_start_dt.strftime("%Y-%m-%d")
+
+                s_start = pytz.utc.localize(s_start_dt)
+
+                if s_start_dt > ns_parser.s_start_date:
+                    print(
+                        f"Using Yahoo Finance with granularity {s_int} the starting date is set to: {s_date_start}\n"
+                    )
+
+                    df_stock = yf.download(
+                        ns_parser.s_ticker,
+                        start=s_date_start,
+                        progress=False,
+                        interval=s_int,
+                        prepost=ns_parser.b_prepost,
+                    )
+
+                else:
+                    df_stock = yf.download(
+                        ns_parser.s_ticker,
+                        start=ns_parser.s_start_date.strftime("%Y-%m-%d"),
+                        progress=False,
+                        interval=s_int,
+                        prepost=ns_parser.b_prepost,
+                    )
+
+                # Check that a stock was successfully retrieved
+                if df_stock.empty:
+                    print("")
+                    return [s_ticker, s_start, s_interval, df_stock]
+
+                df_stock = df_stock.rename(
+                    columns={
+                        "Open": "1. open",
+                        "High": "2. high",
+                        "Low": "3. low",
+                        "Close": "4. close",
+                        "Adj Close": "5. adjusted close",
+                        "Volume": "6. volume",
+                    }
                 )
+                df_stock.index.name = "date"
+
                 return [s_ticker, s_start, s_interval, df_stock]
 
     except Exception as e:

From d6e159b189ef80e56c08284a47fde66cf0f34225 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 01:58:35 +0100
Subject: [PATCH 07/64] refactor description of load

---
 gamestonk_terminal/main_helper.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 0a790667af31..f21ee9443f96 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -119,7 +119,8 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock):
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="load",
-        description=""" Load a stock in order to perform analysis. To load an Indian ticker use '.NS' at the end, e.g. 'SBIN.NS'. """,
+        description=""" Load a stock in order to perform analysis.
+                        To load an Indian ticker use '.NS' at the end, e.g. 'SBIN.NS'. """,
     )
     parser.add_argument(
         "-t",

From 9843182ed9ddd296308441e9fb100df583c9f208 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 02:00:43 +0100
Subject: [PATCH 08/64] import order

---
 gamestonk_terminal/main_helper.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index f21ee9443f96..a0553d087d29 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -1,12 +1,12 @@
 import argparse
 from sys import stdout
+from datetime import datetime, timedelta
 import matplotlib.pyplot as plt
 import pandas as pd
 from alpha_vantage.timeseries import TimeSeries
 import mplfinance as mpf
 import yfinance as yf
 import pytz
-from datetime import datetime, timedelta
 
 from gamestonk_terminal.helper_funcs import (
     valid_date,

From 83fa0aad9e81fca664814710615ab652cdcefc23 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 10:15:30 +0100
Subject: [PATCH 09/64] Add comment regarding loading from different market
 exchanges

---
 gamestonk_terminal/README.md      | 2 +-
 gamestonk_terminal/main_helper.py | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 95f9d7ce9057..4ab6d9c57909 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -23,7 +23,7 @@ The main menu allows the following commands:
 ```
 load -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}]
 ```
-   * Load stock ticker to perform analysis on. To load an Indian ticker use '.NS' at the end, e.g. 'SBIN.NS'
+   * Load stock ticker to perform analysis on. When the data source is 'yf', an Indian ticker can be loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.
      * -s : The starting date (format YYYY-MM-DD) of the stock
      * -i : Intraday stock minutes
      * --source : Source of historical data. 'yf' and 'av' available. Default 'yf'
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index a0553d087d29..0481cb34ffcc 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -119,8 +119,9 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock):
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="load",
-        description=""" Load a stock in order to perform analysis.
-                        To load an Indian ticker use '.NS' at the end, e.g. 'SBIN.NS'. """,
+        description="Load stock ticker to perform analysis on. When the data source is 'yf', an Indian ticker can be"
+                    " loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in"
+                    " https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.",
     )
     parser.add_argument(
         "-t",

From c6f7f801d1d4419b7a3b2dd7fd39fe3b4080d384 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 10:20:42 +0100
Subject: [PATCH 10/64] black formatting

---
 gamestonk_terminal/main_helper.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 0481cb34ffcc..81238c99223d 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -120,8 +120,8 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock):
         add_help=False,
         prog="load",
         description="Load stock ticker to perform analysis on. When the data source is 'yf', an Indian ticker can be"
-                    " loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in"
-                    " https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.",
+        " loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in"
+        " https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.",
     )
     parser.add_argument(
         "-t",

From e6b02c421565e47d2ee40204b619dc2696b10824 Mon Sep 17 00:00:00 2001
From: DidierRLopes <dro.lopes@campus.fct.unl.pt>
Date: Sun, 11 Apr 2021 10:21:14 +0100
Subject: [PATCH 11/64] Fix spelling

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a19102e31825..54c5089fe7f5 100644
--- a/README.md
+++ b/README.md
@@ -224,7 +224,7 @@ Please note that the package `pmdarima` needs to installed through `pip install`
 
 ### Update Terminal
 
-The terminal is constantly being updated with new features and bug fixes, hence, for your terminal to be updaate, you can run:
+The terminal is constantly being updated with new features and bug fixes, hence, for your terminal to be update, you can run:
 ```
 git pull
 ```

From ccd322d6b60c9755154b3d86ed1b47f815c8dc12 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Sun, 11 Apr 2021 16:15:11 -0400
Subject: [PATCH 12/64] add basic portfolio optimization controller

---
 .../comparison_analysis/ca_controller.py      |   8 +
 gamestonk_terminal/main_helper.py             |   1 +
 .../portfolio_optimization/README.md          |  16 ++
 .../portfolio_optimization/__init__.py        |   0
 .../portfolio_optimization/port_opt_api.py    |  28 +++
 .../port_optimization_controller.py           | 160 ++++++++++++++++++
 terminal.py                                   |   6 +
 7 files changed, 219 insertions(+)
 create mode 100644 gamestonk_terminal/portfolio_optimization/README.md
 create mode 100644 gamestonk_terminal/portfolio_optimization/__init__.py
 create mode 100644 gamestonk_terminal/portfolio_optimization/port_opt_api.py
 create mode 100644 gamestonk_terminal/portfolio_optimization/port_optimization_controller.py

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 67b91f8b4dac..9c4aaa89258e 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -15,6 +15,7 @@
 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 port_optimization_controller
 from gamestonk_terminal.menu import session
 from prompt_toolkit.completion import NestedCompleter
 
@@ -42,6 +43,7 @@ class ComparisonAnalysisController:
         "ownership",
         "performance",
         "technical",
+        "po"
     ]
 
     def __init__(
@@ -107,6 +109,8 @@ def print_help(self):
         print("   performance   brief performance comparison")
         print("   technical     brief technical comparison")
         print("")
+        print("   po            portfolio optimzation from selected tickers")
+        print("")
         return
 
     @staticmethod
@@ -312,6 +316,10 @@ def call_technical(self, other_args: List[str]):
         """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"""
+        port_optimization_controller.menu([self.ticker] + self.similar)
+
 
 def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
     """Comparison Analysis Menu"""
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 81238c99223d..8b152e09909c 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -60,6 +60,7 @@ 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 analysis, \t\t UPDATE ME BEFORE PR")
 
     if s_ticker:
         print(
diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
new file mode 100644
index 000000000000..b546337b1be1
--- /dev/null
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -0,0 +1,16 @@
+# Portfolio Optimization
+
+This menu aims to discover optimal portfolios for selected stocks
+
+
+###add
+Adds tickers to current list for optimization
+````
+usage: add ticker1,ticker2,...
+````
+###optimize
+Runs optimization
+````
+usage: optimize
+````
+
diff --git a/gamestonk_terminal/portfolio_optimization/__init__.py b/gamestonk_terminal/portfolio_optimization/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
new file mode 100644
index 000000000000..c4654af99655
--- /dev/null
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -0,0 +1,28 @@
+""" Portfolio Optimization Functions"""
+__docformat__ = "numpy"
+
+import argparse
+from typing import List
+
+def optimize(list_of_stocks:List, other_args:List[str]):
+    """
+
+    Parameters
+    ----------
+    list_of_stocks: List[str]
+        List of tickers to be included in optimization
+
+    Returns
+    -------
+    weights : dict
+        Dictionary of weights where keys are the tickers
+
+    """
+    weights = {}
+    n_stocks = len(list_of_stocks)
+    for stock in list_of_stocks:
+        weights[stock] = round(1/n_stocks, 5)
+
+    return weights
+
+
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
new file mode 100644
index 000000000000..b90bb4b8b8d0
--- /dev/null
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -0,0 +1,160 @@
+"""Portfolio Optimization Controller Module"""
+__docformat__ = "numpy"
+
+
+import argparse
+from typing import List, Set
+from datetime import datetime
+
+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 port_opt_api as po_api
+from prompt_toolkit.completion import NestedCompleter
+
+class PortfolioOptimization:
+
+    CHOICES  = ["help",
+                "q",
+                "quit",
+                "clear",
+                "add",
+                "optimize"]
+
+    def __init__(self,
+                 tickers:Set[str] = None
+                 ):
+        """
+        Construct Portfolio Optimization
+        """
+
+        self.po_parser = argparse.ArgumentParser(add_help=False, prog="po")
+        self.po_parser.add_argument("cmd", choices=self.CHOICES )
+        self.tickers = set(tickers)
+
+    @staticmethod
+    def print_help(tickers: Set[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("   optimize     run optimization")
+        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]):
+        self.add_stocks(self, other_args)
+
+    def call_optimize(self, other_args:List[str]):
+        weights = po_api.optimize(self.tickers, other_args)
+        print("Optimal Weights for Equal Weighting:")
+        print(weights)
+        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:
+            # For the case where a user uses: 'select NIO,XPEV,LI'
+            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
+
+            for ticker in ns_parser.add_tickers:
+                self.tickers.add(ticker)
+
+
+            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 = []
+    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
+
diff --git a/terminal.py b/terminal.py
index 91fa02c2e94d..123c73a78daf 100644
--- a/terminal.py
+++ b/terminal.py
@@ -31,6 +31,7 @@
 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 port_optimization_controller as 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:

From f2af23ded15d54d4afee93bce93db6d435e5f00a Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Sun, 11 Apr 2021 17:09:53 -0400
Subject: [PATCH 13/64] round1 edits

---
 .../comparison_analysis/ca_controller.py      |  6 ++--
 gamestonk_terminal/main_helper.py             |  2 +-
 .../portfolio_optimization/port_opt_api.py    | 31 ++++++++++++++++++-
 .../port_optimization_controller.py           | 23 +++++++++++---
 4 files changed, 53 insertions(+), 9 deletions(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 9c4aaa89258e..39329a01155a 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -15,7 +15,7 @@
 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 port_optimization_controller
+from gamestonk_terminal.portfolio_optimization import port_optimization_controller as po_controller
 from gamestonk_terminal.menu import session
 from prompt_toolkit.completion import NestedCompleter
 
@@ -109,7 +109,7 @@ def print_help(self):
         print("   performance   brief performance comparison")
         print("   technical     brief technical comparison")
         print("")
-        print("   po            portfolio optimzation from selected tickers")
+        print("   >po           portfolio optimzation from selected tickers")
         print("")
         return
 
@@ -318,7 +318,7 @@ def call_technical(self, other_args: List[str]):
 
     def call_po(self, _):
         """Open Portfolio Optimization menu with ticker and similar"""
-        port_optimization_controller.menu([self.ticker] + self.similar)
+        return po_controller.menu([self.ticker] + self.similar)
 
 
 def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 8b152e09909c..57289ec70f48 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -60,7 +60,7 @@ 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 analysis, \t\t UPDATE ME BEFORE PR")
+    print("   po          portfolio analysis, \t\t menu to explore portfolio optimizations")
 
     if s_ticker:
         print(
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index c4654af99655..862f45dc0bc9 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -4,7 +4,7 @@
 import argparse
 from typing import List
 
-def optimize(list_of_stocks:List, other_args:List[str]):
+def optimize(list_of_stocks:List[str], other_args:List[str]):
     """
 
     Parameters
@@ -18,6 +18,14 @@ def optimize(list_of_stocks:List, other_args:List[str]):
         Dictionary of weights where keys are the tickers
 
     """
+
+    parser = argparse.ArgumentParser(
+        add_help=False,
+        prog="optimize",
+    )
+
+    parse
+
     weights = {}
     n_stocks = len(list_of_stocks)
     for stock in list_of_stocks:
@@ -25,4 +33,25 @@ def optimize(list_of_stocks:List, other_args:List[str]):
 
     return weights
 
+def equal_weight(list_of_stocks:List[str]):
+    """
+        Equally weighted portfolio, where weight = 1/# of stocks
+
+        Parameters
+        ----------
+        list_of_stocks: List[str]
+            List of tickers to be included in optimization
+
+        Returns
+        -------
+        weights : dict
+            Dictionary of weights where keys are the tickers
+
+        """
+    weights = {}
+    n_stocks = len(list_of_stocks)
+    for stock in list_of_stocks:
+        weights[stock] = round(1 / n_stocks, 5)
+
+    return weights
 
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index b90bb4b8b8d0..5892fb7f58c5 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -17,9 +17,10 @@ class PortfolioOptimization:
     CHOICES  = ["help",
                 "q",
                 "quit",
-                "clear",
+                "select",
                 "add",
-                "optimize"]
+                "equal_weight",
+                "mkt_cap"]
 
     def __init__(self,
                  tickers:Set[str] = None
@@ -46,7 +47,11 @@ def print_help(tickers: Set[str]):
         )
         print("")
         print("   add          add ticker to optimize")
-        print("   optimize     run optimization")
+        print("   select       overwrite current tickers with new tickers")
+        print("")
+        print("Optimization:")
+        print("   equal_weight   equally weighted portfolio")
+        print("   mkt_cap        marketcap weighted portfolio")
         print("")
 
 
@@ -86,6 +91,16 @@ def call_optimize(self, other_args:List[str]):
         print("Optimal Weights for Equal Weighting:")
         print(weights)
         print("")
+    def call_equal_weight(self, other_args:List[str]):
+        weights = po_api.equal_weight(self.tickers, other_args)
+        print("Optimal Weights for Equal Weighting:")
+        print(weights)
+        print("")
+
+    def call_mkt_cap(self,other_args:List[str]):
+        print("TODO")
+    def call_select(self,other_args:List[str]):
+        print("TODO")
 
     @staticmethod
     def add_stocks(self, other_args: List[str]):
@@ -105,7 +120,7 @@ def add_stocks(self, other_args: List[str]):
             help="add tickers to optimzation.",
         )
         try:
-            # For the case where a user uses: 'select NIO,XPEV,LI'
+
             if other_args:
                 if "-" not in other_args[0]:
                     other_args.insert(0, "-t")

From abda4bf7e5fb76111a2f8a0a1354d24a24c90bcd Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Sun, 11 Apr 2021 17:57:51 -0400
Subject: [PATCH 14/64] added select/mktcap options

---
 .../portfolio_optimization/port_opt_api.py    | 56 +++++++++----------
 .../port_optimization_controller.py           | 15 ++---
 2 files changed, 34 insertions(+), 37 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 862f45dc0bc9..7c7e8d9f3d46 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -3,37 +3,9 @@
 
 import argparse
 from typing import List
+import yfinance as yf
 
-def optimize(list_of_stocks:List[str], other_args:List[str]):
-    """
-
-    Parameters
-    ----------
-    list_of_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="optimize",
-    )
-
-    parse
-
-    weights = {}
-    n_stocks = len(list_of_stocks)
-    for stock in list_of_stocks:
-        weights[stock] = round(1/n_stocks, 5)
-
-    return weights
-
-def equal_weight(list_of_stocks:List[str]):
+def equal_weight(list_of_stocks:List[str], other_args:List[str]):
     """
         Equally weighted portfolio, where weight = 1/# of stocks
 
@@ -55,3 +27,27 @@ def equal_weight(list_of_stocks:List[str]):
 
     return weights
 
+def market_cap_weighting(list_of_stocks:List[str], other_args:List[str]):
+    """
+    Market cap weighted portfolio where each weight is the stocks market cap/ sum of all market caps
+    Parameters
+    ----------
+    list_of_stocks: List[str[
+        List of tickers to be included in optimization
+
+    Returns
+    -------
+    weights: dict
+        Dictionary of weights where keys are the tickers
+    """
+    weights = {}
+    mkt_cap = {}
+    total_cap = 0
+    for stock in list_of_stocks:
+        stock_cap = yf.Ticker(stock).info["marketCap"]
+        mkt_cap[stock] = stock_cap
+        total_cap += stock_cap
+    for k,v in mkt_cap.items():
+        weights[k] = round(v/total_cap,5)
+
+    return weights
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index 5892fb7f58c5..aad015af3a56 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -86,11 +86,7 @@ def call_quit(self, _):
     def call_add(self, other_args:List[str]):
         self.add_stocks(self, other_args)
 
-    def call_optimize(self, other_args:List[str]):
-        weights = po_api.optimize(self.tickers, other_args)
-        print("Optimal Weights for Equal Weighting:")
-        print(weights)
-        print("")
+
     def call_equal_weight(self, other_args:List[str]):
         weights = po_api.equal_weight(self.tickers, other_args)
         print("Optimal Weights for Equal Weighting:")
@@ -98,9 +94,14 @@ def call_equal_weight(self, other_args:List[str]):
         print("")
 
     def call_mkt_cap(self,other_args:List[str]):
-        print("TODO")
+        weights = po_api.market_cap_weighting(self.tickers, other_args)
+        print("Market Cap Weighting Weights:")
+        print(weights)
+        print("")
+
     def call_select(self,other_args:List[str]):
-        print("TODO")
+        self.tickers = set([])
+        self.add_stocks(self, other_args)
 
     @staticmethod
     def add_stocks(self, other_args: List[str]):

From 83066e8189a1774c2651743c1de725bfc29a698d Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Sun, 11 Apr 2021 19:39:07 -0400
Subject: [PATCH 15/64] update menus to allow switching between po/scr and
 po/ca.  If going ca->po-> ca, the loaded stock remains in the ca menu.

---
 .../comparison_analysis/ca_controller.py      |  5 +-
 gamestonk_terminal/main_helper.py             |  2 +-
 .../port_optimization_controller.py           | 55 ++++++++++++++++++-
 3 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 39329a01155a..2e723970ff37 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -109,7 +109,7 @@ def print_help(self):
         print("   performance   brief performance comparison")
         print("   technical     brief technical comparison")
         print("")
-        print("   >po           portfolio optimzation from selected tickers")
+        print("   > po          portfolio optimization for selected tickers")
         print("")
         return
 
@@ -318,8 +318,7 @@ def call_technical(self, other_args: List[str]):
 
     def call_po(self, _):
         """Open Portfolio Optimization menu with ticker and similar"""
-        return po_controller.menu([self.ticker] + self.similar)
-
+        return po_controller.menu_from_ca(self.ticker, self.similar)
 
 def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
     """Comparison Analysis Menu"""
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index 57289ec70f48..e09a7d2023ca 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -60,7 +60,7 @@ 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 analysis, \t\t menu to explore portfolio optimizations")
+    print("   po          portfolio optimization, \t\t TBA")
 
     if s_ticker:
         print(
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index aad015af3a56..185230680908 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -5,11 +5,14 @@
 import argparse
 from typing import List, Set
 from datetime import datetime
-
+import pandas as pd
 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 port_opt_api as po_api
+from gamestonk_terminal.comparison_analysis import ca_controller
+from gamestonk_terminal.screener import screener_controller
+
 from prompt_toolkit.completion import NestedCompleter
 
 class PortfolioOptimization:
@@ -17,6 +20,8 @@ class PortfolioOptimization:
     CHOICES  = ["help",
                 "q",
                 "quit",
+                "ca",
+                "scr",
                 "select",
                 "add",
                 "equal_weight",
@@ -32,6 +37,9 @@ def __init__(self,
         self.po_parser = argparse.ArgumentParser(add_help=False, prog="po")
         self.po_parser.add_argument("cmd", choices=self.CHOICES )
         self.tickers = 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: Set[str]):
@@ -42,6 +50,8 @@ def print_help(tickers: Set[str]):
             "   q             quit this menu, and shows back to main menu"
         )
         print("   quit          quit to abandon program")
+        print("   > ca          comparison analysis menu")
+        print("   > scr         screener menu")
         print(
             f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}"
         )
@@ -83,10 +93,16 @@ def call_quit(self, _):
         """Process Quit command - quit the program"""
         return True
 
+    def call_ca(self, other_args:List[str]):
+
+        return ca_controller.menu(pd.DataFrame(), self.ca_ticker, "", "1440min")
+
+    def call_scr(self, _):
+        return screener_controller.menu()
+
     def call_add(self, other_args:List[str]):
         self.add_stocks(self, other_args)
 
-
     def call_equal_weight(self, other_args:List[str]):
         weights = po_api.equal_weight(self.tickers, other_args)
         print("Optimal Weights for Equal Weighting:")
@@ -143,6 +159,41 @@ def add_stocks(self, other_args: List[str]):
 
         print("")
 
+    @classmethod
+    def from_ca_menu(cls, ticker: str, similar: List[str]):
+        return cls(set([ticker] + similar))
+
+
+def menu_from_ca(ticker:str, similar:List[str]):
+    """Portfolio Optimization Menu from ca menu that allows for jumping between"""
+    po_controller = PortfolioOptimization.from_ca_menu(ticker,similar)
+    po_controller.ca_ticker = ticker
+    po_controller.ca_similar = similar
+    po_controller.call_help([ticker]+ similar)
+
+    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
+
 
 def menu(tickers:List[str]):
     """Portfolio Optimization Menu"""

From f8e4d06de9d7d2ddcbc20bb44853e4db063d57b9 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Sun, 11 Apr 2021 20:27:54 -0400
Subject: [PATCH 16/64] poetry add + requirement adds

---
 .../portfolio_optimization/port_opt_api.py    |   3 +
 .../port_optimization_controller.py           |   7 +-
 poetry.lock                                   | 173 +++++++++++++++++-
 pyproject.toml                                |   1 +
 requirements-full.txt                         |   1 +
 requirements-old.txt                          |   1 +
 requirements-win.txt                          |   1 +
 requirements.txt                              |   1 +
 8 files changed, 181 insertions(+), 7 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 7c7e8d9f3d46..97c62979e708 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -4,6 +4,9 @@
 import argparse
 from typing import List
 import yfinance as yf
+from pypfopt.efficient_frontier import EfficientFrontier
+from pypfopt import risk_models
+from pypfopt import expected_returns
 
 def equal_weight(list_of_stocks:List[str], other_args:List[str]):
     """
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index 185230680908..ef7c2d1a1285 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -60,9 +60,12 @@ def print_help(tickers: Set[str]):
         print("   select       overwrite current tickers with new tickers")
         print("")
         print("Optimization:")
-        print("   equal_weight   equally weighted portfolio")
-        print("   mkt_cap        marketcap weighted portfolio")
         print("")
+        print("   Property weighted:")
+        print("       equal_weight   equally weighted portfolio")
+        print("       mkt_cap        marketcap weighted portfolio")
+        print("   Mean Variance Optimization")
+        print("        max_sharpe    portfolio with maximum sharpe ratio")
 
 
     def switch(self, an_input: str):
diff --git a/poetry.lock b/poetry.lock
index b20746d369bc..5cbefaf1f5d3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -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"},
diff --git a/pyproject.toml b/pyproject.toml
index 5895d2c9dd09..1082086197e4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"
diff --git a/requirements-full.txt b/requirements-full.txt
index 2bdd5e071dc7..1c51e43294a7 100644
--- a/requirements-full.txt
+++ b/requirements-full.txt
@@ -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
diff --git a/requirements-old.txt b/requirements-old.txt
index f06595a2ee69..3a4b6bb05c43 100644
--- a/requirements-old.txt
+++ b/requirements-old.txt
@@ -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")
diff --git a/requirements-win.txt b/requirements-win.txt
index a5198652fef5..560a2508261b 100644
--- a/requirements-win.txt
+++ b/requirements-win.txt
@@ -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")
diff --git a/requirements.txt b/requirements.txt
index b89711f769c3..4f4ba01310a9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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")

From 811f56887573353e1980dac6e04569ca2108ef03 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Sun, 11 Apr 2021 21:07:13 -0400
Subject: [PATCH 17/64] added max sharpe + black

---
 .../comparison_analysis/ca_controller.py      |  7 +-
 .../portfolio_optimization/port_opt_api.py    | 79 ++++++++++++-----
 .../port_optimization_controller.py           | 85 ++++++++++---------
 3 files changed, 110 insertions(+), 61 deletions(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 2e723970ff37..7db095a40385 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -15,7 +15,9 @@
 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 port_optimization_controller as po_controller
+from gamestonk_terminal.portfolio_optimization import (
+    port_optimization_controller as po_controller,
+)
 from gamestonk_terminal.menu import session
 from prompt_toolkit.completion import NestedCompleter
 
@@ -43,7 +45,7 @@ class ComparisonAnalysisController:
         "ownership",
         "performance",
         "technical",
-        "po"
+        "po",
     ]
 
     def __init__(
@@ -320,6 +322,7 @@ def call_po(self, _):
         """Open Portfolio Optimization menu with ticker and similar"""
         return po_controller.menu_from_ca(self.ticker, self.similar)
 
+
 def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
     """Comparison Analysis Menu"""
 
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 97c62979e708..71e697b73cc4 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -3,26 +3,28 @@
 
 import argparse
 from typing import List
+import pandas as pd
 import yfinance as yf
 from pypfopt.efficient_frontier import EfficientFrontier
 from pypfopt import risk_models
 from pypfopt import expected_returns
 
-def equal_weight(list_of_stocks:List[str], other_args:List[str]):
+
+def equal_weight(list_of_stocks: List[str], other_args: List[str]):
     """
-        Equally weighted portfolio, where weight = 1/# of stocks
+    Equally weighted portfolio, where weight = 1/# of stocks
 
-        Parameters
-        ----------
-        list_of_stocks: List[str]
-            List of tickers to be included in optimization
+    Parameters
+    ----------
+    list_of_stocks: List[str]
+        List of tickers to be included in optimization
 
-        Returns
-        -------
-        weights : dict
-            Dictionary of weights where keys are the tickers
+    Returns
+    -------
+    weights : dict
+        Dictionary of weights where keys are the tickers
 
-        """
+    """
     weights = {}
     n_stocks = len(list_of_stocks)
     for stock in list_of_stocks:
@@ -30,13 +32,19 @@ def equal_weight(list_of_stocks:List[str], other_args:List[str]):
 
     return weights
 
-def market_cap_weighting(list_of_stocks:List[str], other_args:List[str]):
+
+def property_weighting(
+    list_of_stocks: List[str], property_type: str, other_args: List[str]
+):
     """
-    Market cap weighted portfolio where each weight is the stocks market cap/ sum of all market caps
+    Property weighted portfolio where each weight is the relative fraction.  Examples
     Parameters
     ----------
-    list_of_stocks: List[str[
+    list_of_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
     -------
@@ -44,13 +52,42 @@ def market_cap_weighting(list_of_stocks:List[str], other_args:List[str]):
         Dictionary of weights where keys are the tickers
     """
     weights = {}
-    mkt_cap = {}
-    total_cap = 0
+    prop = {}
+    prop_sum = 0
+
+    for stock in list_of_stocks:
+        stock_prop = yf.Ticker(stock).info["marketCap"]
+        prop[stock] = stock_prop
+        prop_sum += stock_prop
+    for k, v in prop.items():
+        weights[k] = round(v / prop_sum, 5)
+
+    return weights
+
+
+def max_sharpe(list_of_stocks: List[str], other_args: List[str]):
+    """
+    Return a portfolio that maximizes the sharpe ratio.  Currently defaulting to 3m of historical data
+    Parameters
+    ----------
+    list_of_stocks: List[str]
+        List of the stocks to be included in the weights
+
+    Returns
+    -------
+    weights: dict
+        Dictionary of weights where keys are the tickers.
+    """
+
+    df = yf.download(list_of_stocks, period="3mo", group_by="ticker")
+    df1 = pd.DataFrame(index=df.index)
+    # process df
     for stock in list_of_stocks:
-        stock_cap = yf.Ticker(stock).info["marketCap"]
-        mkt_cap[stock] = stock_cap
-        total_cap += stock_cap
-    for k,v in mkt_cap.items():
-        weights[k] = round(v/total_cap,5)
+        df1[stock] = df[stock]["Adj Close"]
 
+    mu = expected_returns.mean_historical_return(df1)
+    S = risk_models.sample_cov(df1)
+    ef = EfficientFrontier(mu, S)
+    weights = ef.max_sharpe()
+    ef.portfolio_performance(verbose=True)
     return weights
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index ef7c2d1a1285..6c7db144f96c 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -15,27 +15,30 @@
 
 from prompt_toolkit.completion import NestedCompleter
 
+
 class PortfolioOptimization:
 
-    CHOICES  = ["help",
-                "q",
-                "quit",
-                "ca",
-                "scr",
-                "select",
-                "add",
-                "equal_weight",
-                "mkt_cap"]
-
-    def __init__(self,
-                 tickers:Set[str] = None
-                 ):
+    CHOICES = [
+        "help",
+        "q",
+        "quit",
+        "ca",
+        "scr",
+        "select",
+        "add",
+        "equal_weight",
+        "mkt_cap",
+        "div_yield",
+        "max_sharpe",
+    ]
+
+    def __init__(self, tickers: Set[str] = None):
         """
         Construct Portfolio Optimization
         """
 
         self.po_parser = argparse.ArgumentParser(add_help=False, prog="po")
-        self.po_parser.add_argument("cmd", choices=self.CHOICES )
+        self.po_parser.add_argument("cmd", choices=self.CHOICES)
         self.tickers = set(tickers)
         # These will allow the ca menu to be re-access
         self.ca_ticker = None
@@ -46,15 +49,11 @@ def print_help(tickers: Set[str]):
         """Print help"""
         print("\nPortfolio Optimization:")
         print("   help          show this menu again")
-        print(
-            "   q             quit this menu, and shows back to main menu"
-        )
+        print("   q             quit this menu, and shows back to main menu")
         print("   quit          quit to abandon program")
         print("   > ca          comparison analysis menu")
         print("   > scr         screener menu")
-        print(
-            f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}"
-        )
+        print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}")
         print("")
         print("   add          add ticker to optimize")
         print("   select       overwrite current tickers with new tickers")
@@ -64,9 +63,9 @@ def print_help(tickers: Set[str]):
         print("   Property weighted:")
         print("       equal_weight   equally weighted portfolio")
         print("       mkt_cap        marketcap weighted portfolio")
-        print("   Mean Variance Optimization")
-        print("        max_sharpe    portfolio with maximum sharpe ratio")
-
+        print("       div_yield      dividend weighted portfolio\n")
+        print("   Mean Variance Optimization :")
+        print("        max_sharpe    portfolio with maximum sharpe ratio\n")
 
     def switch(self, an_input: str):
         """Process and dispatch input
@@ -96,31 +95,43 @@ def call_quit(self, _):
         """Process Quit command - quit the program"""
         return True
 
-    def call_ca(self, other_args:List[str]):
+    def call_ca(self, other_args: List[str]):
 
         return ca_controller.menu(pd.DataFrame(), self.ca_ticker, "", "1440min")
 
     def call_scr(self, _):
         return screener_controller.menu()
 
-    def call_add(self, other_args:List[str]):
+    def call_add(self, other_args: List[str]):
         self.add_stocks(self, other_args)
 
-    def call_equal_weight(self, other_args:List[str]):
+    def call_select(self, other_args: List[str]):
+        self.tickers = set([])
+        self.add_stocks(self, other_args)
+
+    def call_equal_weight(self, other_args: List[str]):
         weights = po_api.equal_weight(self.tickers, other_args)
         print("Optimal Weights for Equal Weighting:")
         print(weights)
         print("")
 
-    def call_mkt_cap(self,other_args:List[str]):
-        weights = po_api.market_cap_weighting(self.tickers, other_args)
+    def call_mkt_cap(self, other_args: List[str]):
+        weights = po_api.property_weighting(self.tickers, "marketCap", other_args)
         print("Market Cap Weighting Weights:")
         print(weights)
         print("")
 
-    def call_select(self,other_args:List[str]):
-        self.tickers = set([])
-        self.add_stocks(self, other_args)
+    def call_div_yield(self, other_args: List[str]):
+        weights = po_api.property_weighting(self.tickers, "dividendYield", other_args)
+        print("Dividend Weighed Weights:")
+        print(weights)
+        print("")
+
+    def call_max_sharpe(self, other_args: List[str]):
+        weights = po_api.max_sharpe(self.tickers, other_args)
+        print("Maximum Sharpe Weights:")
+        print(weights)
+        print("")
 
     @staticmethod
     def add_stocks(self, other_args: List[str]):
@@ -152,7 +163,6 @@ def add_stocks(self, other_args: List[str]):
             for ticker in ns_parser.add_tickers:
                 self.tickers.add(ticker)
 
-
             print(
                 f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}"
             )
@@ -167,12 +177,12 @@ def from_ca_menu(cls, ticker: str, similar: List[str]):
         return cls(set([ticker] + similar))
 
 
-def menu_from_ca(ticker:str, similar:List[str]):
+def menu_from_ca(ticker: str, similar: List[str]):
     """Portfolio Optimization Menu from ca menu that allows for jumping between"""
-    po_controller = PortfolioOptimization.from_ca_menu(ticker,similar)
+    po_controller = PortfolioOptimization.from_ca_menu(ticker, similar)
     po_controller.ca_ticker = ticker
     po_controller.ca_similar = similar
-    po_controller.call_help([ticker]+ similar)
+    po_controller.call_help([ticker] + similar)
 
     while True:
         # Get input command from user
@@ -198,9 +208,9 @@ def menu_from_ca(ticker:str, similar:List[str]):
             continue
 
 
-def menu(tickers:List[str]):
+def menu(tickers: List[str]):
     """Portfolio Optimization Menu"""
-    if tickers == ['']:
+    if tickers == [""]:
         tickers = []
     po_controller = PortfolioOptimization(tickers)
     po_controller.call_help(tickers)
@@ -227,4 +237,3 @@ def menu(tickers:List[str]):
         except SystemExit:
             print("The command selected doesn't exist\n")
             continue
-

From 0ee0b77ab9d152e369eef49e395984b46dbaba28 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Sun, 11 Apr 2021 22:17:10 -0700
Subject: [PATCH 18/64] Refactoring Due Diligence controller

---
 .../due_diligence/dd_controller.py            | 219 ++++++++++++++++++
 gamestonk_terminal/due_diligence/dd_menu.py   | 148 ------------
 terminal.py                                   |   4 +-
 3 files changed, 221 insertions(+), 150 deletions(-)
 create mode 100644 gamestonk_terminal/due_diligence/dd_controller.py
 delete mode 100644 gamestonk_terminal/due_diligence/dd_menu.py

diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
new file mode 100644
index 000000000000..8f8e8371cb78
--- /dev/null
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -0,0 +1,219 @@
+""" Due Diligence Controller """
+__docformat__ = "numpy"
+
+import argparse
+from typing import List
+from pandas.core.frame import DataFrame
+from prompt_toolkit.completion import NestedCompleter
+
+from gamestonk_terminal.due_diligence import business_insider_api as bi_api
+from gamestonk_terminal.due_diligence import financial_modeling_prep_api as fmp_api
+from gamestonk_terminal.due_diligence import finviz_api as fvz_api
+from gamestonk_terminal.due_diligence import market_watch_api as mw_api
+from gamestonk_terminal.due_diligence import quandl_api as q_api
+from gamestonk_terminal.due_diligence import reddit_api as r_api
+from gamestonk_terminal.due_diligence import news_api
+from gamestonk_terminal import feature_flags as gtff
+from gamestonk_terminal.helper_funcs import get_flair
+from gamestonk_terminal.menu import session
+
+
+class DueDiligenceController:
+    """ Due Diligence Controller """
+
+    # Command choices
+    CHOICES = [
+        "help",
+        "q",
+        "quit",
+        "red",
+        "short",
+        "rating",
+        "pt",
+        "est",
+        "ins",
+        "insider",
+        "news",
+        "analyst",
+        "warnings",
+        "sec",
+    ]
+
+    def __init__(self, stock: DataFrame, ticker: str, start: str, interval: str):
+        """Constructor
+
+        Parameters
+        ----------
+        stock : DataFrame
+            Due diligence stock dataframe
+        ticker : str
+            Due diligence ticker symbol
+        start : str
+            Start date of the stock data
+        interval : str
+            Stock data interval
+        """
+        self.stock = stock
+        self.ticker = ticker
+        self.start = start
+        self.interval = interval
+        self.dd_parser = argparse.ArgumentParser(add_help=False, prog="dd")
+        self.dd_parser.add_argument(
+            "cmd",
+            choices=self.CHOICES,
+        )
+
+    def print_help(self):
+        """ Print help """
+
+        intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"]
+
+        if self.start:
+            print(
+                f"\n{intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})"
+            )
+        else:
+            print(f"\n{intraday} Stock: {self.ticker}")
+
+        print("\nDue Diligence:")
+        print("   help          show this fundamental analysis menu again")
+        print("   q             quit this menu, and shows back to main menu")
+        print("   quit          quit to abandon program")
+        print("")
+        print("   news          latest news of the company [News API]")
+        print("   red           gets due diligence from another user's post [Reddit]")
+        print("   analyst       analyst prices and ratings of the company [Finviz]")
+        print(
+            "   rating        rating of the company from strong sell to strong buy [FMP]"
+        )
+        print("   pt            price targets over time [Business Insider]")
+        print(
+            "   est           quarter and year analysts earnings estimates [Business Insider]"
+        )
+        print("   ins           insider activity over time [Business Insider]")
+        print("   insider       insider trading of the company [Finviz]")
+        print("   sec           SEC filings [Market Watch]")
+        print("   short         short interest [Quandl]")
+        print(
+            "   warnings      company warnings according to Sean Seah book [Market Watch]"
+        )
+        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.dd_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()
+
+    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_red(self, other_args: List[str]):
+        """ Process red command """
+        r_api.due_diligence(other_args, self.ticker)
+
+    def call_insider(self, other_args: List[str]):
+        """ Process insider command """
+        fvz_api.insider(other_args, self.ticker)
+
+    def call_news(self, other_args: List[str]):
+        """ Process news command """
+        news_api.news(other_args, self.ticker)
+
+    def call_analyst(self, other_args: List[str]):
+        """ Process analyst command """
+        fvz_api.analyst(other_args, self.ticker)
+
+    def call_pt(self, other_args: List[str]):
+        """ Process pt command """
+        bi_api.price_target_from_analysts(
+            other_args, self.stock, self.ticker, self.start, self.interval
+        )
+
+    def call_est(self, other_args: List[str]):
+        """ Process est command """
+        bi_api.estimates(other_args, self.ticker)
+
+    def call_ins(self, other_args: List[str]):
+        """ Process ins command """
+        bi_api.insider_activity(
+            other_args, self.stock, self.ticker, self.start, self.interval
+        )
+
+    def call_rating(self, other_args: List[str]):
+        """ Process rating command """
+        fmp_api.rating(other_args, self.ticker)
+
+    def call_warnings(self, other_args: List[str]):
+        """ Process rating command """
+        mw_api.sean_seah_warnings(other_args, self.ticker)
+
+    def call_sec(self, other_args: List[str]):
+        """ Process sec command """
+        mw_api.sec_fillings(other_args, self.ticker)
+
+    def call_short(self, other_args: List[str]):
+        """ Process short command """
+        q_api.short_interest(other_args, self.ticker, self.start)
+
+
+def menu(stock: DataFrame, ticker: str, start: str, interval: str):
+    """Due Diligence Menu
+
+    Parameters
+    ----------
+    stock : DataFrame
+        Due diligence stock dataframe
+    ticker : str
+        Due diligence ticker symbol
+    start : str
+        Start date of the stock data
+    interval : str
+        Stock data interval
+    """
+
+    dd_controller = DueDiligenceController(stock, ticker, start, interval)
+    dd_controller.call_help(None)
+
+    while True:
+        # Get input command from user
+        if session and gtff.USE_PROMPT_TOOLKIT:
+            completer = NestedCompleter.from_nested_dict(
+                {c: None for c in dd_controller.CHOICES}
+            )
+
+            an_input = session.prompt(
+                f"{get_flair()} (dd)> ",
+                completer=completer,
+            )
+        else:
+            an_input = input(f"{get_flair()} (dd)> ")
+
+        try:
+            process_input = dd_controller.switch(an_input)
+
+            if process_input is not None:
+                return process_input
+
+        except SystemExit:
+            print("The command selected doesn't exist\n")
+            continue
diff --git a/gamestonk_terminal/due_diligence/dd_menu.py b/gamestonk_terminal/due_diligence/dd_menu.py
deleted file mode 100644
index 94635e6d8c8a..000000000000
--- a/gamestonk_terminal/due_diligence/dd_menu.py
+++ /dev/null
@@ -1,148 +0,0 @@
-import argparse
-
-from gamestonk_terminal.due_diligence import business_insider_api as bi_api
-from gamestonk_terminal.due_diligence import financial_modeling_prep_api as fmp_api
-from gamestonk_terminal.due_diligence import finviz_api as fvz_api
-from gamestonk_terminal.due_diligence import market_watch_api as mw_api
-from gamestonk_terminal.due_diligence import quandl_api as q_api
-from gamestonk_terminal.due_diligence import reddit_api as r_api
-from gamestonk_terminal.due_diligence import news_api
-from gamestonk_terminal import feature_flags as gtff
-from gamestonk_terminal.helper_funcs import get_flair
-from gamestonk_terminal.menu import session
-from prompt_toolkit.completion import NestedCompleter
-
-
-def print_due_diligence(s_ticker, s_start, s_interval):
-    """ Print help """
-
-    s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"]
-
-    if s_start:
-        print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})")
-    else:
-        print(f"\n{s_intraday} Stock: {s_ticker}")
-
-    print("\nDue Diligence:")
-    print("   help          show this fundamental analysis menu again")
-    print("   q             quit this menu, and shows back to main menu")
-    print("   quit          quit to abandon program")
-    print("")
-    print("   news          latest news of the company [News API]")
-    print("   red           gets due diligence from another user's post [Reddit]")
-    print("   analyst       analyst prices and ratings of the company [Finviz]")
-    print("   rating        rating of the company from strong sell to strong buy [FMP]")
-    print("   pt            price targets over time [Business Insider]")
-    print(
-        "   est           quarter and year analysts earnings estimates [Business Insider]"
-    )
-    print("   ins           insider activity over time [Business Insider]")
-    print("   insider       insider trading of the company [Finviz]")
-    print("   sec           SEC filings [Market Watch]")
-    print("   short         short interest [Quandl]")
-    print(
-        "   warnings      company warnings according to Sean Seah book [Market Watch]"
-    )
-    print("")
-    return
-
-
-def dd_menu(df_stock, s_ticker, s_start, s_interval):
-
-    # Add list of arguments that the due diligence parser accepts
-    dd_parser = argparse.ArgumentParser(prog="dd", add_help=False)
-    choices = [
-        "info",
-        "help",
-        "q",
-        "quit",
-        "red",
-        "short",
-        "rating",
-        "pt",
-        "est",
-        "ins",
-        "insider",
-        "news",
-        "analyst",
-        "warnings",
-        "sec",
-    ]
-    dd_parser.add_argument("cmd", choices=choices)
-    completer = NestedCompleter.from_nested_dict({c: None for c in choices})
-
-    print_due_diligence(s_ticker, s_start, s_interval)
-
-    # Loop forever and ever
-    while True:
-        # Get input command from user
-        if session and gtff.USE_PROMPT_TOOLKIT:
-            as_input = session.prompt(
-                f"{get_flair()} (dd)> ",
-                completer=completer,
-            )
-        else:
-            as_input = input(f"{get_flair()} (dd)> ")
-
-        # Parse due diligence command of the list of possible commands
-        try:
-            (ns_known_args, l_args) = dd_parser.parse_known_args(as_input.split())
-
-        except SystemExit:
-            print("The command selected doesn't exist\n")
-            continue
-
-        if ns_known_args.cmd == "help":
-            print_due_diligence(s_ticker, s_start, s_interval)
-
-        elif ns_known_args.cmd == "q":
-            # Just leave the DD menu
-            return False
-
-        elif ns_known_args.cmd == "quit":
-            # Abandon the program
-            return True
-
-        # REDDIT API
-        elif ns_known_args.cmd == "red":
-            r_api.due_diligence(l_args, s_ticker)
-
-        # FINVIZ API
-        elif ns_known_args.cmd == "insider":
-            fvz_api.insider(l_args, s_ticker)
-
-        elif ns_known_args.cmd == "news":
-            news_api.news(l_args, s_ticker)
-
-        elif ns_known_args.cmd == "analyst":
-            fvz_api.analyst(l_args, s_ticker)
-
-        # BUSINESS INSIDER API
-        elif ns_known_args.cmd == "pt":
-            bi_api.price_target_from_analysts(
-                l_args, df_stock, s_ticker, s_start, s_interval
-            )
-
-        elif ns_known_args.cmd == "est":
-            bi_api.estimates(l_args, s_ticker)
-
-        elif ns_known_args.cmd == "ins":
-            bi_api.insider_activity(l_args, df_stock, s_ticker, s_start, s_interval)
-
-        # FINANCIAL MODELING PREP API
-        elif ns_known_args.cmd == "rating":
-            fmp_api.rating(l_args, s_ticker)
-
-        # MARKET WATCH API
-        elif ns_known_args.cmd == "sec":
-            mw_api.sec_fillings(l_args, s_ticker)
-
-        elif ns_known_args.cmd == "warnings":
-            mw_api.sean_seah_warnings(l_args, s_ticker)
-
-        # QUANDL API
-        elif ns_known_args.cmd == "short":
-            q_api.short_interest(l_args, s_ticker, s_start)
-
-        else:
-            print("Command not recognized!")
diff --git a/terminal.py b/terminal.py
index 91fa02c2e94d..1408473bebc8 100644
--- a/terminal.py
+++ b/terminal.py
@@ -15,7 +15,7 @@
 from gamestonk_terminal import thought_of_the_day as thought
 from gamestonk_terminal import res_menu as rm
 from gamestonk_terminal.discovery import disc_controller
-from gamestonk_terminal.due_diligence import dd_menu as ddm
+from gamestonk_terminal.due_diligence import dd_controller
 from gamestonk_terminal.fundamental_analysis import fa_menu as fam
 from gamestonk_terminal.helper_funcs import b_is_stock_market_open, get_flair
 from gamestonk_terminal.main_helper import clear, export, load, print_help, view, candle
@@ -208,7 +208,7 @@ def main():
             b_quit = ta_controller.menu(df_stock, s_ticker, s_start, s_interval)
 
         elif ns_known_args.opt == "dd":
-            b_quit = ddm.dd_menu(df_stock, s_ticker, s_start, s_interval)
+            b_quit = dd_controller.menu(df_stock, s_ticker, s_start, s_interval)
 
         elif ns_known_args.opt == "eda":
             if s_interval == "1440min":

From 207ca0bf447eabb6c0a5da8229b416b47a12a7bf Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Sun, 11 Apr 2021 23:09:05 -0700
Subject: [PATCH 19/64] Refactoring business_insider_view

---
 ...nsider_api.py => business_insider_view.py} | 125 +++++++++++++-----
 .../due_diligence/dd_controller.py            |   8 +-
 2 files changed, 96 insertions(+), 37 deletions(-)
 rename gamestonk_terminal/due_diligence/{business_insider_api.py => business_insider_view.py} (81%)

diff --git a/gamestonk_terminal/due_diligence/business_insider_api.py b/gamestonk_terminal/due_diligence/business_insider_view.py
similarity index 81%
rename from gamestonk_terminal/due_diligence/business_insider_api.py
rename to gamestonk_terminal/due_diligence/business_insider_view.py
index 63ca11b3dc99..ade39815ab30 100644
--- a/gamestonk_terminal/due_diligence/business_insider_api.py
+++ b/gamestonk_terminal/due_diligence/business_insider_view.py
@@ -1,6 +1,11 @@
+""" Due Diligence Controller """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 import json
 import re
+from pandas.core.frame import DataFrame
 import requests
 import matplotlib.pyplot as plt
 import pandas as pd
@@ -12,11 +17,30 @@
     get_user_agent,
     parse_known_args_and_warn,
 )
+from gamestonk_terminal import feature_flags as gtff
 
 register_matplotlib_converters()
 
 
-def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval):
+def price_target_from_analysts(
+    other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str
+):
+    """Print analysts' price targets for a given stock
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-n", "10"]
+    stock : DataFrame
+        Due diligence stock dataframe
+    ticker : str
+        Due diligence ticker symbol
+    start : str
+        Start date of the stock data
+    interval : str
+        Stock data interval
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="pt",
@@ -34,12 +58,12 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
         url_market_business_insider = (
-            f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock"
+            f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock"
         )
         text_soup_market_business_insider = BeautifulSoup(
             requests.get(
@@ -73,17 +97,17 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval):
         df_analyst_data = df_analyst_data.set_index("Date")
 
         # Slice start of ratings
-        if s_start:
-            df_analyst_data = df_analyst_data[s_start:]
+        if start:
+            df_analyst_data = df_analyst_data[start:]
 
-        if s_interval == "1440min":
-            plt.plot(df_stock.index, df_stock["5. adjusted close"].values, lw=3)
+        if interval == "1440min":
+            plt.plot(stock.index, stock["5. adjusted close"].values, lw=3)
         # Intraday
         else:
-            plt.plot(df_stock.index, df_stock["4. close"].values, lw=3)
+            plt.plot(stock.index, stock["4. close"].values, lw=3)
 
-        if s_start:
-            plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[s_start:])
+        if start:
+            plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[start:])
         else:
             plt.plot(df_analyst_data.groupby(by=["Date"]).mean())
 
@@ -91,13 +115,17 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval):
 
         plt.legend(["Closing Price", "Average Price Target", "Price Target"])
 
-        plt.title(f"{s_ticker} (Time Series) and Price Target")
-        plt.xlim(df_stock.index[0], df_stock.index[-1])
+        plt.title(f"{ticker} (Time Series) and Price Target")
+        plt.xlim(stock.index[0], stock.index[-1])
         plt.xlabel("Time")
         plt.ylabel("Share Price ($)")
         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("")
 
@@ -115,7 +143,17 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval):
         return
 
 
-def estimates(l_args, s_ticker):
+def estimates(other_args: List[str], ticker: str):
+    """Print analysts' estimates for a given ticker
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args
+    ticker : str
+        Due diligence ticker symbol
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="est",
@@ -123,12 +161,12 @@ def estimates(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
         url_market_business_insider = (
-            f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock"
+            f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock"
         )
         text_soup_market_business_insider = BeautifulSoup(
             requests.get(
@@ -274,7 +312,24 @@ def estimates(l_args, s_ticker):
         return
 
 
-def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
+def insider_activity(
+    other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str
+):
+    """Print insider activity
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-n", "10"]
+    stock : DataFrame
+        Due diligence stock dataframe
+    ticker : str
+        Due diligence ticker symbol
+    start : str
+        Start date of the stock data
+    interval : str
+        Stock data interval
+    """
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="ins",
@@ -291,12 +346,12 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
         url_market_business_insider = (
-            f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock"
+            f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock"
         )
         text_soup_market_business_insider = BeautifulSoup(
             requests.get(
@@ -334,17 +389,17 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
         df_insider = df_insider.set_index("Date")
         df_insider = df_insider.sort_index(ascending=True)
 
-        if s_start:
-            df_insider = df_insider[s_start:]
+        if start:
+            df_insider = df_insider[start:]
 
         _, ax = plt.subplots()
 
-        if s_interval == "1440min":
-            plt.plot(df_stock.index, df_stock["5. adjusted close"].values, lw=3)
+        if interval == "1440min":
+            plt.plot(stock.index, stock["5. adjusted close"].values, lw=3)
         else:  # Intraday
-            plt.plot(df_stock.index, df_stock["4. close"].values, lw=3)
+            plt.plot(stock.index, stock["4. close"].values, lw=3)
 
-        plt.title(f"{s_ticker.upper()} (Time Series) and Price Target")
+        plt.title(f"{ticker.upper()} (Time Series) and Price Target")
 
         plt.xlabel("Time")
         plt.ylabel("Share Price ($)")
@@ -354,7 +409,7 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
             * float(row["Shares Traded"].replace(",", "")),
             axis=1,
         )
-        plt.xlim(df_insider.index[0], df_stock.index[-1])
+        plt.xlim(df_insider.index[0], stock.index[-1])
         min_price, max_price = ax.get_ylim()
 
         price_range = max_price - min_price
@@ -373,16 +428,16 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
         for ind in (
             df_insider[df_insider["Type"] == "Sell"].groupby(by=["Date"]).sum().index
         ):
-            if ind in df_stock.index:
+            if ind in stock.index:
                 ind_dt = ind
             else:
                 ind_dt = get_next_stock_market_days(ind, 1)[0]
 
             n_stock_price = 0
-            if s_interval == "1440min":
-                n_stock_price = df_stock["5. adjusted close"][ind_dt]
+            if interval == "1440min":
+                n_stock_price = stock["5. adjusted close"][ind_dt]
             else:
-                n_stock_price = df_stock["4. close"][ind_dt]
+                n_stock_price = stock["4. close"][ind_dt]
 
             plt.vlines(
                 x=ind_dt,
@@ -402,16 +457,16 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
         for ind in (
             df_insider[df_insider["Type"] == "Buy"].groupby(by=["Date"]).sum().index
         ):
-            if ind in df_stock.index:
+            if ind in stock.index:
                 ind_dt = ind
             else:
                 ind_dt = get_next_stock_market_days(ind, 1)[0]
 
             n_stock_price = 0
-            if s_interval == "1440min":
-                n_stock_price = df_stock["5. adjusted close"][ind_dt]
+            if interval == "1440min":
+                n_stock_price = stock["5. adjusted close"][ind_dt]
             else:
-                n_stock_price = df_stock["4. close"][ind_dt]
+                n_stock_price = stock["4. close"][ind_dt]
 
             plt.vlines(
                 x=ind_dt,
@@ -431,6 +486,10 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval):
         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()
 
         l_names = list()
diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
index 8f8e8371cb78..bc3a85379a48 100644
--- a/gamestonk_terminal/due_diligence/dd_controller.py
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -6,7 +6,7 @@
 from pandas.core.frame import DataFrame
 from prompt_toolkit.completion import NestedCompleter
 
-from gamestonk_terminal.due_diligence import business_insider_api as bi_api
+from gamestonk_terminal.due_diligence import business_insider_view as bi_view
 from gamestonk_terminal.due_diligence import financial_modeling_prep_api as fmp_api
 from gamestonk_terminal.due_diligence import finviz_api as fvz_api
 from gamestonk_terminal.due_diligence import market_watch_api as mw_api
@@ -145,17 +145,17 @@ def call_analyst(self, other_args: List[str]):
 
     def call_pt(self, other_args: List[str]):
         """ Process pt command """
-        bi_api.price_target_from_analysts(
+        bi_view.price_target_from_analysts(
             other_args, self.stock, self.ticker, self.start, self.interval
         )
 
     def call_est(self, other_args: List[str]):
         """ Process est command """
-        bi_api.estimates(other_args, self.ticker)
+        bi_view.estimates(other_args, self.ticker)
 
     def call_ins(self, other_args: List[str]):
         """ Process ins command """
-        bi_api.insider_activity(
+        bi_view.insider_activity(
             other_args, self.stock, self.ticker, self.start, self.interval
         )
 

From 7397c757755670121854a0c5fbc85893d7ef74b9 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Sun, 11 Apr 2021 23:15:35 -0700
Subject: [PATCH 20/64] Adding mypy exceptions

---
 gamestonk_terminal/due_diligence/business_insider_view.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/gamestonk_terminal/due_diligence/business_insider_view.py b/gamestonk_terminal/due_diligence/business_insider_view.py
index ade39815ab30..03d965a3774d 100644
--- a/gamestonk_terminal/due_diligence/business_insider_view.py
+++ b/gamestonk_terminal/due_diligence/business_insider_view.py
@@ -83,7 +83,7 @@ def price_target_from_analysts(
                 d_analyst_data = json.loads(s_analyst_data)
                 break
 
-        # pprint.pprint(d_analyst_data)
+        # type: ignore
         df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"])
         df_analyst_data = df_analyst_data[
             ["DateLabel", "Company", "InternalRating", "PriceTarget"]
@@ -98,6 +98,7 @@ def price_target_from_analysts(
 
         # Slice start of ratings
         if start:
+            # type: ignore
             df_analyst_data = df_analyst_data[start:]
 
         if interval == "1440min":
@@ -107,6 +108,7 @@ def price_target_from_analysts(
             plt.plot(stock.index, stock["4. close"].values, lw=3)
 
         if start:
+            # type: ignore
             plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[start:])
         else:
             plt.plot(df_analyst_data.groupby(by=["Date"]).mean())
@@ -390,6 +392,7 @@ def insider_activity(
         df_insider = df_insider.sort_index(ascending=True)
 
         if start:
+            # type: ignore
             df_insider = df_insider[start:]
 
         _, ax = plt.subplots()

From 83477c78fbf84f32318d0a01746c3059f534c99b Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Sun, 11 Apr 2021 23:24:09 -0700
Subject: [PATCH 21/64] Correcting mypy exceptions

---
 .../due_diligence/business_insider_view.py           | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/gamestonk_terminal/due_diligence/business_insider_view.py b/gamestonk_terminal/due_diligence/business_insider_view.py
index 03d965a3774d..68f4f573a502 100644
--- a/gamestonk_terminal/due_diligence/business_insider_view.py
+++ b/gamestonk_terminal/due_diligence/business_insider_view.py
@@ -83,8 +83,7 @@ def price_target_from_analysts(
                 d_analyst_data = json.loads(s_analyst_data)
                 break
 
-        # type: ignore
-        df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"])
+        df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"])  # type: ignore
         df_analyst_data = df_analyst_data[
             ["DateLabel", "Company", "InternalRating", "PriceTarget"]
         ]
@@ -98,8 +97,7 @@ def price_target_from_analysts(
 
         # Slice start of ratings
         if start:
-            # type: ignore
-            df_analyst_data = df_analyst_data[start:]
+            df_analyst_data = df_analyst_data[start:]  # type: ignore
 
         if interval == "1440min":
             plt.plot(stock.index, stock["5. adjusted close"].values, lw=3)
@@ -108,8 +106,7 @@ def price_target_from_analysts(
             plt.plot(stock.index, stock["4. close"].values, lw=3)
 
         if start:
-            # type: ignore
-            plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[start:])
+            plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[start:])  # type: ignore
         else:
             plt.plot(df_analyst_data.groupby(by=["Date"]).mean())
 
@@ -392,8 +389,7 @@ def insider_activity(
         df_insider = df_insider.sort_index(ascending=True)
 
         if start:
-            # type: ignore
-            df_insider = df_insider[start:]
+            df_insider = df_insider[start:]  # type: ignore
 
         _, ax = plt.subplots()
 

From 005a1978c6116108b275e8f2b2d283333cf2d6b2 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Mon, 12 Apr 2021 12:58:54 +0100
Subject: [PATCH 22/64] Discovery controller to close figures when in menu

---
 gamestonk_terminal/discovery/disc_controller.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/gamestonk_terminal/discovery/disc_controller.py b/gamestonk_terminal/discovery/disc_controller.py
index c7a4ac40e331..07bb33a33022 100644
--- a/gamestonk_terminal/discovery/disc_controller.py
+++ b/gamestonk_terminal/discovery/disc_controller.py
@@ -4,6 +4,7 @@
 import argparse
 import os
 from typing import List
+from matplotlib import 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
@@ -209,6 +210,8 @@ def menu():
             an_input = input(f"{get_flair()} (disc)> ")
 
         try:
+            plt.close("all")
+
             process_input = disc_controller.switch(an_input)
 
             if process_input is not None:

From 3ea2fed7b09447d146ce8f0b5dea286b0a61fcb3 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 08:37:34 -0700
Subject: [PATCH 23/64] Refactoring financial_modeling_prep_view

---
 .../due_diligence/business_insider_view.py    |  6 ++---
 .../due_diligence/dd_controller.py            |  4 ++--
 ...api.py => financial_modeling_prep_view.py} | 22 +++++++++++++++----
 tests/test_dd_financial_modeling_prep_api.py  |  9 --------
 4 files changed, 23 insertions(+), 18 deletions(-)
 rename gamestonk_terminal/due_diligence/{financial_modeling_prep_api.py => financial_modeling_prep_view.py} (65%)
 delete mode 100644 tests/test_dd_financial_modeling_prep_api.py

diff --git a/gamestonk_terminal/due_diligence/business_insider_view.py b/gamestonk_terminal/due_diligence/business_insider_view.py
index 68f4f573a502..fd83d023cc3f 100644
--- a/gamestonk_terminal/due_diligence/business_insider_view.py
+++ b/gamestonk_terminal/due_diligence/business_insider_view.py
@@ -25,7 +25,7 @@
 def price_target_from_analysts(
     other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str
 ):
-    """Print analysts' price targets for a given stock
+    """Display analysts' price targets for a given stock
 
     Parameters
     ----------
@@ -143,7 +143,7 @@ def price_target_from_analysts(
 
 
 def estimates(other_args: List[str], ticker: str):
-    """Print analysts' estimates for a given ticker
+    """Display analysts' estimates for a given ticker
 
     Parameters
     ----------
@@ -314,7 +314,7 @@ def estimates(other_args: List[str], ticker: str):
 def insider_activity(
     other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str
 ):
-    """Print insider activity
+    """Display insider activity
 
     Parameters
     ----------
diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
index bc3a85379a48..deebd355a4df 100644
--- a/gamestonk_terminal/due_diligence/dd_controller.py
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -7,7 +7,7 @@
 from prompt_toolkit.completion import NestedCompleter
 
 from gamestonk_terminal.due_diligence import business_insider_view as bi_view
-from gamestonk_terminal.due_diligence import financial_modeling_prep_api as fmp_api
+from gamestonk_terminal.due_diligence import financial_modeling_prep_view as fmp_view
 from gamestonk_terminal.due_diligence import finviz_api as fvz_api
 from gamestonk_terminal.due_diligence import market_watch_api as mw_api
 from gamestonk_terminal.due_diligence import quandl_api as q_api
@@ -161,7 +161,7 @@ def call_ins(self, other_args: List[str]):
 
     def call_rating(self, other_args: List[str]):
         """ Process rating command """
-        fmp_api.rating(other_args, self.ticker)
+        fmp_view.rating(other_args, self.ticker)
 
     def call_warnings(self, other_args: List[str]):
         """ Process rating command """
diff --git a/gamestonk_terminal/due_diligence/financial_modeling_prep_api.py b/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py
similarity index 65%
rename from gamestonk_terminal/due_diligence/financial_modeling_prep_api.py
rename to gamestonk_terminal/due_diligence/financial_modeling_prep_view.py
index 9163bf021494..68cb7aae1c46 100644
--- a/gamestonk_terminal/due_diligence/financial_modeling_prep_api.py
+++ b/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py
@@ -1,10 +1,24 @@
+""" Due Diligence Controller """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 import FundamentalAnalysis as fa  # Financial Modeling Prep
 from gamestonk_terminal import config_terminal as cfg
 from gamestonk_terminal.helper_funcs import parse_known_args_and_warn
 
 
-def rating(l_args, s_ticker):
+def rating(other_args: List[str], ticker: str):
+    """Display ratings for a given ticker
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args
+    ticker : str
+        Stock ticker
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="rating",
@@ -16,18 +30,18 @@ def rating(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
-        df_fa = fa.rating(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP)
+        df_fa = fa.rating(ticker, cfg.API_KEY_FINANCIALMODELINGPREP)
         print(df_fa)
 
         print("")
 
     except KeyError:
         print(
-            f"Financialmodelingprep.com is returning empty response the ticker {s_ticker}."
+            f"Financialmodelingprep.com is returning empty response the ticker {ticker}."
         )
         print("")
         return
diff --git a/tests/test_dd_financial_modeling_prep_api.py b/tests/test_dd_financial_modeling_prep_api.py
deleted file mode 100644
index 952fecdfb73c..000000000000
--- a/tests/test_dd_financial_modeling_prep_api.py
+++ /dev/null
@@ -1,9 +0,0 @@
-""" fundamental_analysis/business_insider_api.py tests """
-import unittest
-
-from gamestonk_terminal.due_diligence.financial_modeling_prep_api import rating
-
-
-class TestDdFinancialModelingPrepApi(unittest.TestCase):
-    def test_rating(self):
-        rating([], "PLTR")

From 7b5e7d165b04a52ed8456c688c43f8aaf5dc77ac Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 09:26:59 -0700
Subject: [PATCH 24/64] Refactoring finviz_view

---
 .../due_diligence/dd_controller.py            |  6 +-
 .../{finviz_api.py => finviz_view.py}         | 81 ++++++++++++++++---
 tests/test_dd_financial_modeling_prep_view.py |  9 +++
 ...d_finviz_api.py => test_dd_finviz_view.py} |  2 +-
 4 files changed, 83 insertions(+), 15 deletions(-)
 rename gamestonk_terminal/due_diligence/{finviz_api.py => finviz_view.py} (70%)
 create mode 100644 tests/test_dd_financial_modeling_prep_view.py
 rename tests/{test_dd_finviz_api.py => test_dd_finviz_view.py} (70%)

diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
index deebd355a4df..814af2afb6b8 100644
--- a/gamestonk_terminal/due_diligence/dd_controller.py
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -8,7 +8,7 @@
 
 from gamestonk_terminal.due_diligence import business_insider_view as bi_view
 from gamestonk_terminal.due_diligence import financial_modeling_prep_view as fmp_view
-from gamestonk_terminal.due_diligence import finviz_api as fvz_api
+from gamestonk_terminal.due_diligence import finviz_view as fvz_view
 from gamestonk_terminal.due_diligence import market_watch_api as mw_api
 from gamestonk_terminal.due_diligence import quandl_api as q_api
 from gamestonk_terminal.due_diligence import reddit_api as r_api
@@ -133,7 +133,7 @@ def call_red(self, other_args: List[str]):
 
     def call_insider(self, other_args: List[str]):
         """ Process insider command """
-        fvz_api.insider(other_args, self.ticker)
+        fvz_view.insider(other_args, self.ticker)
 
     def call_news(self, other_args: List[str]):
         """ Process news command """
@@ -141,7 +141,7 @@ def call_news(self, other_args: List[str]):
 
     def call_analyst(self, other_args: List[str]):
         """ Process analyst command """
-        fvz_api.analyst(other_args, self.ticker)
+        fvz_view.analyst(other_args, self.ticker)
 
     def call_pt(self, other_args: List[str]):
         """ Process pt command """
diff --git a/gamestonk_terminal/due_diligence/finviz_api.py b/gamestonk_terminal/due_diligence/finviz_view.py
similarity index 70%
rename from gamestonk_terminal/due_diligence/finviz_api.py
rename to gamestonk_terminal/due_diligence/finviz_view.py
index 50b47b433ee4..4e02d5298ba8 100644
--- a/gamestonk_terminal/due_diligence/finviz_api.py
+++ b/gamestonk_terminal/due_diligence/finviz_view.py
@@ -1,4 +1,8 @@
+""" Due Diligence Controller """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 from colorama import Fore, Style
 import finviz
 import pandas as pd
@@ -11,6 +15,19 @@
 
 
 def category_color_red_green(val: str) -> str:
+    """Add color to analyst rating
+
+    Parameters
+    ----------
+    val : str
+        Analyst rating - Upgrade/Downgrade
+
+    Returns
+    -------
+    str
+        Analyst rating with color
+    """
+
     if val == "Upgrade":
         return Fore.GREEN + val + Style.RESET_ALL
     if val == "Downgrade":
@@ -18,7 +35,16 @@ def category_color_red_green(val: str) -> str:
     return val
 
 
-def insider(l_args, s_ticker):
+def insider(other_args: List[str], ticker: str):
+    """Display insider activity for a given stock ticker
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-n", "10"]
+    ticker : str
+        Stock ticker
+    """
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="insider",
@@ -39,11 +65,11 @@ def insider(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
-        d_finviz_insider = finviz.get_insider(s_ticker)
+        d_finviz_insider = finviz.get_insider(ticker)
         df_fa = pd.DataFrame.from_dict(d_finviz_insider)
         df_fa.set_index("Date", inplace=True)
         df_fa = df_fa[
@@ -68,7 +94,17 @@ def insider(l_args, s_ticker):
         return
 
 
-def news(l_args, s_ticker):
+def news(other_args: List[str], ticker: str):
+    """Display news for a given stock ticker
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-n", "10"]
+    ticker : str
+        Stock ticker
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="news",
@@ -88,11 +124,11 @@ def news(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
-        d_finviz_news = finviz.get_news(s_ticker)
+        d_finviz_news = finviz.get_news(ticker)
         i = 0
         for s_news_title, s_news_link in {*d_finviz_news}:
             print(f"-> {s_news_title}")
@@ -110,9 +146,22 @@ def news(l_args, s_ticker):
         return
 
 
-def analyst_df(s_ticker: str) -> DataFrame:
+def analyst_df(ticker: str) -> DataFrame:
+    """[summary]
+
+    Parameters
+    ----------
+    ticker : str
+        Stock ticker
+
+    Returns
+    -------
+    DataFrame
+        [description]
+    """
+
     try:
-        d_finviz_analyst_price = finviz.get_analyst_price_targets(s_ticker)
+        d_finviz_analyst_price = finviz.get_analyst_price_targets(ticker)
         df_fa = pd.DataFrame.from_dict(d_finviz_analyst_price)
         df_fa.set_index("date", inplace=True)
     except Exception as e:
@@ -122,7 +171,17 @@ def analyst_df(s_ticker: str) -> DataFrame:
     return df_fa
 
 
-def analyst(l_args, s_ticker):
+def analyst(other_args, ticker):
+    """Display analyst ratings
+
+    Parameters
+    ----------
+    other_args : [type]
+        argparse other args
+    ticker : [type]
+        Stock ticker
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="analyst",
@@ -144,11 +203,11 @@ def analyst(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
-        df_fa = analyst_df(s_ticker)
+        df_fa = analyst_df(ticker)
 
         if ns_parser.n_color == 1:
             df_fa["category"] = df_fa["category"].apply(category_color_red_green)
diff --git a/tests/test_dd_financial_modeling_prep_view.py b/tests/test_dd_financial_modeling_prep_view.py
new file mode 100644
index 000000000000..0db0c2996992
--- /dev/null
+++ b/tests/test_dd_financial_modeling_prep_view.py
@@ -0,0 +1,9 @@
+""" fundamental_analysis/business_insider_api.py tests """
+import unittest
+
+from gamestonk_terminal.due_diligence.financial_modeling_prep_view import rating
+
+
+class TestDdFinancialModelingPrepView(unittest.TestCase):
+    def test_rating(self):
+        rating([], "PLTR")
diff --git a/tests/test_dd_finviz_api.py b/tests/test_dd_finviz_view.py
similarity index 70%
rename from tests/test_dd_finviz_api.py
rename to tests/test_dd_finviz_view.py
index b0e1e680b2eb..8c0625100291 100644
--- a/tests/test_dd_finviz_api.py
+++ b/tests/test_dd_finviz_view.py
@@ -1,7 +1,7 @@
 """ due_diligence/finviz_api.py tests """
 import unittest
 
-from gamestonk_terminal.due_diligence.finviz_api import analyst
+from gamestonk_terminal.due_diligence.finviz_view import analyst
 
 
 class TestDdFinvizApi(unittest.TestCase):

From 108ce43ac0a1e793d4f53b010d31edb84220be58 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 09:51:05 -0700
Subject: [PATCH 25/64] Refactor market_watch_view

---
 ...rket_watch_api.py => market_watch_view.py} | 38 +++++++++++++++----
 1 file changed, 31 insertions(+), 7 deletions(-)
 rename gamestonk_terminal/due_diligence/{market_watch_api.py => market_watch_view.py} (93%)

diff --git a/gamestonk_terminal/due_diligence/market_watch_api.py b/gamestonk_terminal/due_diligence/market_watch_view.py
similarity index 93%
rename from gamestonk_terminal/due_diligence/market_watch_api.py
rename to gamestonk_terminal/due_diligence/market_watch_view.py
index f9cfd0549831..cb53ef760cbd 100644
--- a/gamestonk_terminal/due_diligence/market_watch_api.py
+++ b/gamestonk_terminal/due_diligence/market_watch_view.py
@@ -1,4 +1,8 @@
+""" Due Diligence Controller """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 import re
 import requests
 import pandas as pd
@@ -13,7 +17,17 @@
 )
 
 
-def sec_fillings(l_args, s_ticker):
+def sec_fillings(other_args: List[str], ticker: str):
+    """Display SEC filings for a given stock ticker
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-n", "10"]
+    ticker : str
+        Stock ticker
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="sec",
@@ -34,13 +48,13 @@ def sec_fillings(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
         pd.set_option("display.max_colwidth", None)
 
-        url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/secfilings"
+        url_financials = f"https://www.marketwatch.com/investing/stock/{ticker}/financials/secfilings"
 
         text_soup_financials = BeautifulSoup(
             requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text,
@@ -83,7 +97,17 @@ def sec_fillings(l_args, s_ticker):
         return
 
 
-def sean_seah_warnings(l_args, s_ticker):
+def sean_seah_warnings(other_args: List[str], ticker: str):
+    """Display Sean Seah warnings
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args
+    ticker : str
+        Stock ticker
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="warnings",
@@ -114,7 +138,7 @@ def sean_seah_warnings(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
@@ -129,7 +153,7 @@ def sean_seah_warnings(l_args, s_ticker):
 
         # From INCOME STATEMENT, get: 'EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA'
         url_financials = (
-            f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/income"
+            f"https://www.marketwatch.com/investing/stock/{ticker}/financials/income"
         )
         text_soup_financials = BeautifulSoup(
             requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text,
@@ -173,7 +197,7 @@ def sean_seah_warnings(l_args, s_ticker):
         ]
 
         # From BALANCE SHEET, get: 'Liabilities & Shareholders\' Equity', 'Long-Term Debt'
-        url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/balance-sheet"
+        url_financials = f"https://www.marketwatch.com/investing/stock/{ticker}/financials/balance-sheet"
         text_soup_financials = BeautifulSoup(
             requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text,
             "lxml",

From 9344f21e7334777dbd9e54c35d3d8ccf773a612b Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 09:52:48 -0700
Subject: [PATCH 26/64] Update docstrings

---
 gamestonk_terminal/due_diligence/business_insider_view.py       | 2 +-
 .../due_diligence/financial_modeling_prep_view.py               | 2 +-
 gamestonk_terminal/due_diligence/finviz_view.py                 | 2 +-
 gamestonk_terminal/due_diligence/market_watch_view.py           | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/gamestonk_terminal/due_diligence/business_insider_view.py b/gamestonk_terminal/due_diligence/business_insider_view.py
index fd83d023cc3f..32a1995198ca 100644
--- a/gamestonk_terminal/due_diligence/business_insider_view.py
+++ b/gamestonk_terminal/due_diligence/business_insider_view.py
@@ -1,4 +1,4 @@
-""" Due Diligence Controller """
+""" Business Insider View """
 __docformat__ = "numpy"
 
 import argparse
diff --git a/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py b/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py
index 68cb7aae1c46..4026649e2fe2 100644
--- a/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py
+++ b/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py
@@ -1,4 +1,4 @@
-""" Due Diligence Controller """
+""" Financial Modeling Prep View """
 __docformat__ = "numpy"
 
 import argparse
diff --git a/gamestonk_terminal/due_diligence/finviz_view.py b/gamestonk_terminal/due_diligence/finviz_view.py
index 4e02d5298ba8..56a3eaa8a210 100644
--- a/gamestonk_terminal/due_diligence/finviz_view.py
+++ b/gamestonk_terminal/due_diligence/finviz_view.py
@@ -1,4 +1,4 @@
-""" Due Diligence Controller """
+""" FinViz View """
 __docformat__ = "numpy"
 
 import argparse
diff --git a/gamestonk_terminal/due_diligence/market_watch_view.py b/gamestonk_terminal/due_diligence/market_watch_view.py
index cb53ef760cbd..301a3c81aa9a 100644
--- a/gamestonk_terminal/due_diligence/market_watch_view.py
+++ b/gamestonk_terminal/due_diligence/market_watch_view.py
@@ -1,4 +1,4 @@
-""" Due Diligence Controller """
+""" Market Watch View """
 __docformat__ = "numpy"
 
 import argparse

From 3cf740bf8518dbcd135ae2f19617dffa6c5a5090 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 09:56:37 -0700
Subject: [PATCH 27/64] Refactor news_view

---
 .../{news_api.py => news_view.py}             | 22 +++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)
 rename gamestonk_terminal/due_diligence/{news_api.py => news_view.py} (78%)

diff --git a/gamestonk_terminal/due_diligence/news_api.py b/gamestonk_terminal/due_diligence/news_view.py
similarity index 78%
rename from gamestonk_terminal/due_diligence/news_api.py
rename to gamestonk_terminal/due_diligence/news_view.py
index 7d61da029251..9b82d67caae7 100644
--- a/gamestonk_terminal/due_diligence/news_api.py
+++ b/gamestonk_terminal/due_diligence/news_view.py
@@ -1,4 +1,8 @@
+""" News View """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 from datetime import datetime, timedelta
 import requests
 from gamestonk_terminal import config_terminal as cfg
@@ -8,7 +12,17 @@
 )
 
 
-def news(l_args, s_ticker):
+def news(other_args: List[str], ticker: str):
+    """Display news 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="news",
@@ -28,14 +42,14 @@ def news(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
         s_from = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
 
         response = requests.get(
-            f"https://newsapi.org/v2/everything?q={s_ticker}&from={s_from}"
+            f"https://newsapi.org/v2/everything?q={ticker}&from={s_from}"
             f"&sortBy=publishedAt&language=en&apiKey={cfg.API_NEWS_TOKEN}",
         )
 
@@ -45,7 +59,7 @@ def news(l_args, s_ticker):
 
         else:
             print(
-                f"{response.json()['totalResults']} news articles from {s_ticker} were found since {s_from}\n"
+                f"{response.json()['totalResults']} news articles from {ticker} were found since {s_from}\n"
             )
 
             for idx, article in enumerate(response.json()["articles"]):

From d0b6907731de6a393d298396d158eb3ce4ab7df8 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 09:57:14 -0700
Subject: [PATCH 28/64] Refactor news_view

---
 gamestonk_terminal/due_diligence/dd_controller.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
index 814af2afb6b8..59f771f21f7a 100644
--- a/gamestonk_terminal/due_diligence/dd_controller.py
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -12,7 +12,7 @@
 from gamestonk_terminal.due_diligence import market_watch_api as mw_api
 from gamestonk_terminal.due_diligence import quandl_api as q_api
 from gamestonk_terminal.due_diligence import reddit_api as r_api
-from gamestonk_terminal.due_diligence import news_api
+from gamestonk_terminal.due_diligence import news_view
 from gamestonk_terminal import feature_flags as gtff
 from gamestonk_terminal.helper_funcs import get_flair
 from gamestonk_terminal.menu import session
@@ -137,7 +137,7 @@ def call_insider(self, other_args: List[str]):
 
     def call_news(self, other_args: List[str]):
         """ Process news command """
-        news_api.news(other_args, self.ticker)
+        news_view.news(other_args, self.ticker)
 
     def call_analyst(self, other_args: List[str]):
         """ Process analyst command """

From 06f5e7aeb373cb61be2aa66bfd1abc8f91050086 Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 10:10:56 -0700
Subject: [PATCH 29/64] Refactor quandl and reddit view

---
 .../due_diligence/dd_controller.py            | 14 +++----
 .../{quandl_api.py => quandl_view.py}         | 40 ++++++++++++++-----
 .../{reddit_api.py => reddit_view.py}         | 20 ++++++++--
 3 files changed, 53 insertions(+), 21 deletions(-)
 rename gamestonk_terminal/due_diligence/{quandl_api.py => quandl_view.py} (77%)
 rename gamestonk_terminal/due_diligence/{reddit_api.py => reddit_view.py} (89%)

diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py
index 59f771f21f7a..14ecab8fc40e 100644
--- a/gamestonk_terminal/due_diligence/dd_controller.py
+++ b/gamestonk_terminal/due_diligence/dd_controller.py
@@ -9,9 +9,9 @@
 from gamestonk_terminal.due_diligence import business_insider_view as bi_view
 from gamestonk_terminal.due_diligence import financial_modeling_prep_view as fmp_view
 from gamestonk_terminal.due_diligence import finviz_view as fvz_view
-from gamestonk_terminal.due_diligence import market_watch_api as mw_api
-from gamestonk_terminal.due_diligence import quandl_api as q_api
-from gamestonk_terminal.due_diligence import reddit_api as r_api
+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 import feature_flags as gtff
 from gamestonk_terminal.helper_funcs import get_flair
@@ -129,7 +129,7 @@ def call_quit(self, _):
 
     def call_red(self, other_args: List[str]):
         """ Process red command """
-        r_api.due_diligence(other_args, self.ticker)
+        r_view.due_diligence(other_args, self.ticker)
 
     def call_insider(self, other_args: List[str]):
         """ Process insider command """
@@ -165,15 +165,15 @@ def call_rating(self, other_args: List[str]):
 
     def call_warnings(self, other_args: List[str]):
         """ Process rating command """
-        mw_api.sean_seah_warnings(other_args, self.ticker)
+        mw_view.sean_seah_warnings(other_args, self.ticker)
 
     def call_sec(self, other_args: List[str]):
         """ Process sec command """
-        mw_api.sec_fillings(other_args, self.ticker)
+        mw_view.sec_fillings(other_args, self.ticker)
 
     def call_short(self, other_args: List[str]):
         """ Process short command """
-        q_api.short_interest(other_args, self.ticker, self.start)
+        q_view.short_interest(other_args, self.ticker, self.start)
 
 
 def menu(stock: DataFrame, ticker: str, start: str, interval: str):
diff --git a/gamestonk_terminal/due_diligence/quandl_api.py b/gamestonk_terminal/due_diligence/quandl_view.py
similarity index 77%
rename from gamestonk_terminal/due_diligence/quandl_api.py
rename to gamestonk_terminal/due_diligence/quandl_view.py
index 7d4254a9f6dc..2bc29f39b577 100644
--- a/gamestonk_terminal/due_diligence/quandl_api.py
+++ b/gamestonk_terminal/due_diligence/quandl_view.py
@@ -1,7 +1,11 @@
+""" Quandl View """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 import quandl
 from matplotlib import pyplot as plt
-import matplotlib.ticker as ticker
+import matplotlib.ticker
 import pandas as pd
 from gamestonk_terminal.helper_funcs import (
     check_positive,
@@ -11,7 +15,19 @@
 from gamestonk_terminal import config_terminal as cfg
 
 
-def short_interest(l_args, s_ticker, s_start):
+def short_interest(other_args: List[str], ticker: str, start: str):
+    """Display short interest for a given ticker and a given start date
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-d", "10"]
+    ticker : str
+        Stock ticker
+    start : str
+        Start date of the stock data
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="short",
@@ -40,18 +56,18 @@ def short_interest(l_args, s_ticker, s_start):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
         quandl.ApiConfig.api_key = cfg.API_KEY_QUANDL
 
         if ns_parser.b_nyse:
-            df_short_interest = quandl.get(f"FINRA/FNYX_{s_ticker}")
+            df_short_interest = quandl.get(f"FINRA/FNYX_{ticker}")
         else:
-            df_short_interest = quandl.get(f"FINRA/FNSQ_{s_ticker}")
+            df_short_interest = quandl.get(f"FINRA/FNSQ_{ticker}")
 
-        df_short_interest = df_short_interest[s_start:]
+        df_short_interest = df_short_interest[start:]
         df_short_interest.columns = [
             "".join(
                 " " + char if char.isupper() else char.strip() for char in idx
@@ -77,18 +93,18 @@ def short_interest(l_args, s_ticker, s_start):
         ax.set_ylabel("Shares")
         ax.set_xlabel("Date")
 
-        if s_start:
+        if start:
             ax.set_title(
-                f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {s_ticker} from {s_start.date()}"
+                f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {ticker} from {start.date()}"
             )
         else:
             ax.set_title(
-                f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {s_ticker}"
+                f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {ticker}"
             )
 
         ax.legend(labels=["Short Volume", "Total Volume"])
         ax.tick_params(axis="both", which="major")
-        ax.yaxis.set_major_formatter(ticker.EngFormatter())
+        ax.yaxis.set_major_formatter(matplotlib.ticker.EngFormatter())
         ax_twin = ax.twinx()
         ax_twin.tick_params(axis="y", colors="green")
         ax_twin.set_ylabel("Percentage of Volume Shorted", color="green")
@@ -98,7 +114,9 @@ def short_interest(l_args, s_ticker, s_start):
             color="green",
         )
         ax_twin.tick_params(axis="y", which="major", color="green")
-        ax_twin.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.0f%%"))
+        ax_twin.yaxis.set_major_formatter(
+            matplotlib.ticker.FormatStrFormatter("%.0f%%")
+        )
         plt.xlim([df_short_interest.index[0], df_short_interest.index[-1]])
 
         df_short_interest["% of Volume Shorted"] = df_short_interest[
diff --git a/gamestonk_terminal/due_diligence/reddit_api.py b/gamestonk_terminal/due_diligence/reddit_view.py
similarity index 89%
rename from gamestonk_terminal/due_diligence/reddit_api.py
rename to gamestonk_terminal/due_diligence/reddit_view.py
index f305abacef76..8b24b5a1d111 100644
--- a/gamestonk_terminal/due_diligence/reddit_api.py
+++ b/gamestonk_terminal/due_diligence/reddit_view.py
@@ -1,4 +1,8 @@
+""" Reddit View """
+__docformat__ = "numpy"
+
 import argparse
+from typing import List
 from datetime import datetime, timedelta
 from psaw import PushshiftAPI
 import praw
@@ -7,7 +11,17 @@
 from gamestonk_terminal.reddit_helpers import print_and_record_reddit_post
 
 
-def due_diligence(l_args, s_ticker):
+def due_diligence(other_args: List[str], ticker: str):
+    """Display Reddit due diligence data for a given ticker
+
+    Parameters
+    ----------
+    other_args : List[str]
+        argparse other args - ["-l", "5"]
+    ticker : str
+        Stock ticker
+    """
+
     parser = argparse.ArgumentParser(
         add_help=False,
         prog="red",
@@ -47,7 +61,7 @@ def due_diligence(l_args, s_ticker):
     )
 
     try:
-        ns_parser = parse_known_args_and_warn(parser, l_args)
+        ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
 
@@ -83,7 +97,7 @@ def due_diligence(l_args, s_ticker):
         ]
 
         submissions = psaw_api.search_submissions(
-            after=int(n_ts_after), subreddit=l_sub_reddits, q=s_ticker, filter=["id"]
+            after=int(n_ts_after), subreddit=l_sub_reddits, q=ticker, filter=["id"]
         )
         d_submission = {}
         n_flair_posts_found = 0

From 8a1fcb329ee74d342953820151ad03a7df95acee Mon Sep 17 00:00:00 2001
From: Artem Veremey <artem@veremey.net>
Date: Mon, 12 Apr 2021 10:15:43 -0700
Subject: [PATCH 30/64] Ignore mypy errors for now

---
 gamestonk_terminal/due_diligence/market_watch_view.py | 4 ++--
 gamestonk_terminal/due_diligence/quandl_view.py       | 4 ++--
 gamestonk_terminal/due_diligence/reddit_view.py       | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/gamestonk_terminal/due_diligence/market_watch_view.py b/gamestonk_terminal/due_diligence/market_watch_view.py
index 301a3c81aa9a..7a080347cb10 100644
--- a/gamestonk_terminal/due_diligence/market_watch_view.py
+++ b/gamestonk_terminal/due_diligence/market_watch_view.py
@@ -74,7 +74,7 @@ def sec_fillings(other_args: List[str], ticker: str):
                 l_financials_info.extend(a_financials[5:-1])
                 l_financials_info.append(financials_info.a["href"])
                 # Append data values to financials
-                df_financials.loc[len(df_financials.index)] = l_financials_info
+                df_financials.loc[len(df_financials.index)] = l_financials_info  # type: ignore
 
             if "Filing Date" in a_financials:
                 l_financials_header = [a_financials[2]]
@@ -86,7 +86,7 @@ def sec_fillings(other_args: List[str], ticker: str):
                 b_ready_to_process_info = True
 
         # Set Filing Date as index
-        df_financials = df_financials.set_index("Filing Date")
+        df_financials = df_financials.set_index("Filing Date")  # type: ignore
 
         print(df_financials.head(n=ns_parser.n_num).to_string())
         print("")
diff --git a/gamestonk_terminal/due_diligence/quandl_view.py b/gamestonk_terminal/due_diligence/quandl_view.py
index 2bc29f39b577..b0ca0f09f676 100644
--- a/gamestonk_terminal/due_diligence/quandl_view.py
+++ b/gamestonk_terminal/due_diligence/quandl_view.py
@@ -67,7 +67,7 @@ def short_interest(other_args: List[str], ticker: str, start: str):
         else:
             df_short_interest = quandl.get(f"FINRA/FNSQ_{ticker}")
 
-        df_short_interest = df_short_interest[start:]
+        df_short_interest = df_short_interest[start:]  # type: ignore
         df_short_interest.columns = [
             "".join(
                 " " + char if char.isupper() else char.strip() for char in idx
@@ -95,7 +95,7 @@ def short_interest(other_args: List[str], ticker: str, start: str):
 
         if start:
             ax.set_title(
-                f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {ticker} from {start.date()}"
+                f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {ticker} from {start.date()}"  # type: ignore
             )
         else:
             ax.set_title(
diff --git a/gamestonk_terminal/due_diligence/reddit_view.py b/gamestonk_terminal/due_diligence/reddit_view.py
index 8b24b5a1d111..5c536add2005 100644
--- a/gamestonk_terminal/due_diligence/reddit_view.py
+++ b/gamestonk_terminal/due_diligence/reddit_view.py
@@ -99,7 +99,7 @@ def due_diligence(other_args: List[str], ticker: str):
         submissions = psaw_api.search_submissions(
             after=int(n_ts_after), subreddit=l_sub_reddits, q=ticker, filter=["id"]
         )
-        d_submission = {}
+        d_submission = {}  # type: ignore
         n_flair_posts_found = 0
         while True:
             submission = next(submissions, None)

From 9045706a3465bed5dbcd5417830e645dee99befa Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 13:44:04 -0400
Subject: [PATCH 31/64] added a plot of ef and a couple other optimization
 techniques. + black

---
 .../portfolio_optimization/port_opt_api.py    | 189 ++++++++++++++++--
 .../portfolio_optimization/port_opt_helper.py |  51 +++++
 .../port_optimization_controller.py           |  45 ++++-
 terminal.py                                   |   6 +-
 4 files changed, 270 insertions(+), 21 deletions(-)
 create mode 100644 gamestonk_terminal/portfolio_optimization/port_opt_helper.py

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 71e697b73cc4..ccf8e3731df4 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -3,11 +3,31 @@
 
 import argparse
 from typing import List
+import matplotlib.pyplot as plt
+import numpy as np
 import pandas as pd
 import yfinance as yf
-from pypfopt.efficient_frontier import EfficientFrontier
+from pypfopt import plotting
 from pypfopt import risk_models
 from pypfopt import expected_returns
+from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale
+from gamestonk_terminal.portfolio_optimization.port_opt_helper import *
+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(list_of_stocks: List[str], other_args: List[str]):
@@ -65,29 +85,166 @@ def property_weighting(
     return weights
 
 
-def max_sharpe(list_of_stocks: List[str], other_args: List[str]):
+def show_ef(list_of_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 {}
+        stock_prices = process_stocks(list_of_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("")
+
+
+def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str]):
     """
-    Return a portfolio that maximizes the sharpe ratio.  Currently defaulting to 3m of historical data
+    Return a portfolio based on condition in port_type  Currently defaulting to 3m of historical data
     Parameters
     ----------
     list_of_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.
     """
 
-    df = yf.download(list_of_stocks, period="3mo", group_by="ticker")
-    df1 = pd.DataFrame(index=df.index)
-    # process df
-    for stock in list_of_stocks:
-        df1[stock] = df[stock]["Adj Close"]
-
-    mu = expected_returns.mean_historical_return(df1)
-    S = risk_models.sample_cov(df1)
-    ef = EfficientFrontier(mu, S)
-    weights = ef.max_sharpe()
-    ef.portfolio_performance(verbose=True)
-    return weights
+    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,
+    )
+
+    if port_type == "max_sharpe":
+        try:
+            ns_parser = parse_known_args_and_warn(parser, other_args)
+            if not ns_parser:
+                return {}
+            period = ns_parser.period
+            stock_prices = process_stocks(list_of_stocks, period)
+            ef = prepare_efficient_frontier(stock_prices)
+            ef_sharpe = dict(ef.max_sharpe())
+            weights = {key: round(value, 5) for key, value in ef_sharpe.items()}
+            print("")
+            ef.portfolio_performance(verbose=True)
+            print("")
+            return weights
+        except Exception as e:
+            print(e)
+            print("")
+    elif port_type == "min_volatility":
+        try:
+            ns_parser = parse_known_args_and_warn(parser, other_args)
+            if not ns_parser:
+                return {}
+            period = ns_parser.period
+            stock_prices = process_stocks(list_of_stocks, period)
+            ef = prepare_efficient_frontier(stock_prices)
+            ef_min_vol = dict(ef.min_volatility())
+            weights = {key: round(value, 5) for key, value in ef_min_vol.items()}
+            print("")
+            ef.portfolio_performance(verbose=True)
+            print("")
+            return weights
+        except Exception as e:
+            print(e)
+            print("")
+            return {}
+
+    elif port_type == "eff_risk":
+
+        parser.add_argument("-r", "--risk", type=float, dest="risk_level", default=0.1)
+        try:
+            ns_parser = parse_known_args_and_warn(parser, other_args)
+            if not ns_parser:
+                return
+
+            stock_prices = process_stocks(list_of_stocks, ns_parser.period)
+            ef = prepare_efficient_frontier(stock_prices)
+            ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
+            weights = {key: round(value, 5) for key, value in ef_eff_risk.items()}
+            print("")
+            ef.portfolio_performance(verbose=True)
+            print("")
+            return weights
+        except Exception as e:
+            print(e)
+            print("")
+            return {}
+    elif port_type == "eff_ret":
+
+        parser.add_argument(
+            "-r", "--return", type=float, dest="target_return", default=0.1
+        )
+        try:
+            ns_parser = parse_known_args_and_warn(parser, other_args)
+            if not ns_parser:
+                return
+            stock_prices = process_stocks(list_of_stocks, ns_parser.period)
+            ef = prepare_efficient_frontier(stock_prices)
+            ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
+            weights = {key: round(value, 5) for key, value in ef_eff_risk.items()}
+            print("")
+            ef.portfolio_performance(verbose=True)
+            print("")
+            return weights
+        except Exception as e:
+            print(e)
+            print("")
+            return {}
+
+    else:
+        raise ValueError("EF Method not found")
+        return {}
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
new file mode 100644
index 000000000000..c5107aa572c4
--- /dev/null
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
@@ -0,0 +1,51 @@
+""" Portfolio Optimization Helper Functions"""
+__docformat__ = "numpy"
+
+from typing import List
+import pandas as pd
+import yfinance as yf
+from pypfopt.efficient_frontier import EfficientFrontier
+from pypfopt import risk_models
+from pypfopt import expected_returns
+
+
+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, group_by="ticker")
+    stock_closes = pd.DataFrame(index=stock_prices.index)
+    # process df
+    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
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index 6c7db144f96c..1adb748df925 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -4,7 +4,7 @@
 
 import argparse
 from typing import List, Set
-from datetime import datetime
+import matplotlib.pyplot as plt
 import pandas as pd
 from gamestonk_terminal import feature_flags as gtff
 from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
@@ -30,6 +30,11 @@ class PortfolioOptimization:
         "mkt_cap",
         "div_yield",
         "max_sharpe",
+        "min_vol",
+        "eff_risk",
+        "eff_ret",
+        "show_ef",
+        "yolo",
     ]
 
     def __init__(self, tickers: Set[str] = None):
@@ -65,7 +70,13 @@ def print_help(tickers: Set[str]):
         print("       mkt_cap        marketcap weighted portfolio")
         print("       div_yield      dividend weighted portfolio\n")
         print("   Mean Variance Optimization :")
-        print("        max_sharpe    portfolio with maximum sharpe ratio\n")
+        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
@@ -128,11 +139,39 @@ def call_div_yield(self, other_args: List[str]):
         print("")
 
     def call_max_sharpe(self, other_args: List[str]):
-        weights = po_api.max_sharpe(self.tickers, other_args)
+        weights = po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
         print("Maximum Sharpe Weights:")
         print(weights)
         print("")
 
+    def call_min_vol(self, other_args: List[str]):
+        weights = po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
+        print("Minimum volatility Weights:")
+        print(weights)
+        print("")
+
+    def call_eff_risk(self, other_args: List[str]):
+        weights = po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
+        print("Weights for max returns at risk level")
+        print(weights)
+        print("")
+
+    def call_eff_ret(self, other_args: List[str]):
+        weights = po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
+        print("Weights for min risk at target returns")
+        print(weights)
+        print("")
+
+    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]):
 
diff --git a/terminal.py b/terminal.py
index 123c73a78daf..b47ba3570265 100644
--- a/terminal.py
+++ b/terminal.py
@@ -31,7 +31,9 @@
 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 port_optimization_controller as po_controller
+from gamestonk_terminal.portfolio_optimization import (
+    port_optimization_controller as po_controller,
+)
 
 # import warnings
 # warnings.simplefilter("always")
@@ -89,7 +91,7 @@ def main():
         "pa",
         "crypto",
         "ra",
-        "po"
+        "po",
     ]
 
     menu_parser.add_argument("opt", choices=choices)

From b5485f8f5e7448f2d835b7f5f63e2747fcb35495 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 14:50:49 -0400
Subject: [PATCH 32/64] Updated ReadME's and roadmap

---
 ROADMAP.md                                    |   7 +-
 gamestonk_terminal/README.md                  |  15 +++
 gamestonk_terminal/main_helper.py             |   4 +-
 .../portfolio_optimization/README.md          | 122 +++++++++++++++++-
 4 files changed, 140 insertions(+), 8 deletions(-)

diff --git a/ROADMAP.md b/ROADMAP.md
index 1642af3fe104..f5c466f3a9a4 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -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
diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 4ab6d9c57909..2b6d79c7b2ea 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -241,6 +241,21 @@ Command|Description|Brokers
 `hold`    | view net holdings across all logins
 
 &nbsp;
+## 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
+
+&nbsp;
+
+
 ## Cryptocurrency [»](cryptocurrency/README.md)
 
 Command|Description
diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py
index e09a7d2023ca..40c8f6714ba9 100644
--- a/gamestonk_terminal/main_helper.py
+++ b/gamestonk_terminal/main_helper.py
@@ -60,7 +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 TBA")
+    print(
+        "   po          portfolio optimization, \t\t optimal portfolio weights from pyportfolioopt"
+    )
 
     if s_ticker:
         print(
diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
index b546337b1be1..84dbd94d42ea 100644
--- a/gamestonk_terminal/portfolio_optimization/README.md
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -1,16 +1,126 @@
 # Portfolio Optimization
 
-This menu aims to discover optimal portfolios for selected stocks
+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 tickers to current list for optimization
+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
+````
+equal_weight
+````
+Returns `{"Ticker" : 1/# of tickers}`
+### market cap weighted
+````
+mkt_cap
+````
+Returns a dictionary where each weight is given as (Company Market Cap)/(Sum all included market caps)
+###dividened yield weighted
+````
+div_yield
+````
+Returns weights based on relative divident yield.
+
+## 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 spefic, 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:
 ````
-usage: add ticker1,ticker2,...
+max_sharpe [-p PERIOD] 
 ````
-###optimize
-Runs optimization
+* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+
+### min_vol
+This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier.
+The usage is:
 ````
-usage: optimize
+min_vol [-p PERIOD] 
 ````
+* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
 
+### eff_risk
+This portfolio maximizes the returns at a given risk tolerance
+The usage is:
+````
+eff_risk [-p PERIOD] [-r --risk RISK_LEVEL]
+````
+* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -r/--risk Risk tolerance.  5% is 0.05.
+
+### eff_ret
+This portfolio minimizes the risk at a given return level
+The usage is:
+````
+eff_ret [-p PERIOD] [-r --return]
+````
+* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -r/--return.  Desired return.  5% is 0.05.
+
+### show_eff
+This function plots random portfolios basd 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 retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -n Number of portfolios to simulate.
\ No newline at end of file

From 60604c26364009b317aa4c78f58ef16843dbf0df Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 15:01:15 -0400
Subject: [PATCH 33/64] black + pylint

---
 .../portfolio_optimization/port_opt_api.py    | 20 +++++++++++--------
 .../port_optimization_controller.py           |  5 ++---
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index ccf8e3731df4..059affe85ebc 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -5,13 +5,16 @@
 from typing import List
 import matplotlib.pyplot as plt
 import numpy as np
-import pandas as pd
 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.port_opt_helper import *
+from gamestonk_terminal.portfolio_optimization.port_opt_helper import (
+    process_stocks,
+    prepare_efficient_frontier,
+)
 from gamestonk_terminal.config_plot import PLOT_DPI
 from gamestonk_terminal import feature_flags as gtff
 
@@ -30,7 +33,7 @@
 ]
 
 
-def equal_weight(list_of_stocks: List[str], other_args: List[str]):
+def equal_weight(list_of_stocks: List[str], _):
     """
     Equally weighted portfolio, where weight = 1/# of stocks
 
@@ -53,9 +56,7 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
     return weights
 
 
-def property_weighting(
-    list_of_stocks: List[str], property_type: str, other_args: List[str]
-):
+def property_weighting(list_of_stocks: List[str], property_type: str, _):
     """
     Property weighted portfolio where each weight is the relative fraction.  Examples
     Parameters
@@ -76,7 +77,7 @@ def property_weighting(
     prop_sum = 0
 
     for stock in list_of_stocks:
-        stock_prop = yf.Ticker(stock).info["marketCap"]
+        stock_prop = yf.Ticker(stock).info[property_type]
         prop[stock] = stock_prop
         prop_sum += stock_prop
     for k, v in prop.items():
@@ -184,6 +185,8 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
         except Exception as e:
             print(e)
             print("")
+            return {}
+
     elif port_type == "min_volatility":
         try:
             ns_parser = parse_known_args_and_warn(parser, other_args)
@@ -198,6 +201,7 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             ef.portfolio_performance(verbose=True)
             print("")
             return weights
+
         except Exception as e:
             print(e)
             print("")
@@ -223,6 +227,7 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             print(e)
             print("")
             return {}
+
     elif port_type == "eff_ret":
 
         parser.add_argument(
@@ -247,4 +252,3 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
 
     else:
         raise ValueError("EF Method not found")
-        return {}
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index 1adb748df925..70b6f8d69e2d 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -6,6 +6,7 @@
 from typing import List, Set
 import matplotlib.pyplot as plt
 import pandas as pd
+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
@@ -13,8 +14,6 @@
 from gamestonk_terminal.comparison_analysis import ca_controller
 from gamestonk_terminal.screener import screener_controller
 
-from prompt_toolkit.completion import NestedCompleter
-
 
 class PortfolioOptimization:
 
@@ -106,7 +105,7 @@ def call_quit(self, _):
         """Process Quit command - quit the program"""
         return True
 
-    def call_ca(self, other_args: List[str]):
+    def call_ca(self, _):
 
         return ca_controller.menu(pd.DataFrame(), self.ca_ticker, "", "1440min")
 

From 93a7216bf098edfbff94c7bea2ae077b65c76345 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 15:04:56 -0400
Subject: [PATCH 34/64] spelling

---
 gamestonk_terminal/portfolio_optimization/README.md | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
index 84dbd94d42ea..6d3e96b5a094 100644
--- a/gamestonk_terminal/portfolio_optimization/README.md
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -74,7 +74,7 @@ Where S is the covariance matrix between stocks and R is the expected returns.
 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 spefic, please submit a feature request, or if you can
+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.
@@ -88,7 +88,7 @@ The usage is:
 ````
 max_sharpe [-p PERIOD] 
 ````
-* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -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.
 
 ### min_vol
 This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier.
@@ -96,7 +96,7 @@ The usage is:
 ````
 min_vol [-p PERIOD] 
 ````
-* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -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.
 
 ### eff_risk
 This portfolio maximizes the returns at a given risk tolerance
@@ -104,7 +104,7 @@ The usage is:
 ````
 eff_risk [-p PERIOD] [-r --risk RISK_LEVEL]
 ````
-* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -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.  5% is 0.05.
 
 ### eff_ret
@@ -113,7 +113,7 @@ The usage is:
 ````
 eff_ret [-p PERIOD] [-r --return]
 ````
-* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -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.  5% is 0.05.
 
 ### show_eff
@@ -122,5 +122,5 @@ The usage is:
 ````
 show_eff [-p PERIOD]  [-n N_PORTFOLIOS]
 ````
-* -p/--period Amount of time to retreive data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defualts to 3mo.
+* -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.
\ No newline at end of file

From ffdb7098127313f6348e4e46ac4af38b166c92cb Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 15:39:56 -0400
Subject: [PATCH 35/64] mypy?

---
 .../portfolio_optimization/port_optimization_controller.py    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index 70b6f8d69e2d..e7cc26b5bc1c 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -36,7 +36,7 @@ class PortfolioOptimization:
         "yolo",
     ]
 
-    def __init__(self, tickers: Set[str] = None):
+    def __init__(self, tickers: List[str] = None):
         """
         Construct Portfolio Optimization
         """
@@ -49,7 +49,7 @@ def __init__(self, tickers: Set[str] = None):
         self.ca_similar = None
 
     @staticmethod
-    def print_help(tickers: Set[str]):
+    def print_help(tickers: List[str]):
         """Print help"""
         print("\nPortfolio Optimization:")
         print("   help          show this menu again")

From 52dab41d7eb088522f164eb0f5d69a8da42fb26a Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 15:42:16 -0400
Subject: [PATCH 36/64] flake

---
 .../portfolio_optimization/port_optimization_controller.py      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index e7cc26b5bc1c..1026c22d5a38 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -3,7 +3,7 @@
 
 
 import argparse
-from typing import List, Set
+from typing import List
 import matplotlib.pyplot as plt
 import pandas as pd
 from prompt_toolkit.completion import NestedCompleter

From dcf87e30170a815d763988b4e567330445a88e32 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 17:08:15 -0400
Subject: [PATCH 37/64] get rid of po -> ca/scr

---
 .../comparison_analysis/ca_controller.py      |  2 +-
 .../port_optimization_controller.py           | 65 +++----------------
 2 files changed, 9 insertions(+), 58 deletions(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 7db095a40385..4d8b7f33670a 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -320,7 +320,7 @@ def call_technical(self, other_args: List[str]):
 
     def call_po(self, _):
         """Open Portfolio Optimization menu with ticker and similar"""
-        return po_controller.menu_from_ca(self.ticker, self.similar)
+        return po_controller.menu([self.ticker] + self.similar)
 
 
 def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
index 1026c22d5a38..18e04383afdd 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
@@ -21,8 +21,6 @@ class PortfolioOptimization:
         "help",
         "q",
         "quit",
-        "ca",
-        "scr",
         "select",
         "add",
         "equal_weight",
@@ -55,8 +53,6 @@ def print_help(tickers: List[str]):
         print("   help          show this menu again")
         print("   q             quit this menu, and shows back to main menu")
         print("   quit          quit to abandon program")
-        print("   > ca          comparison analysis menu")
-        print("   > scr         screener menu")
         print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}")
         print("")
         print("   add          add ticker to optimize")
@@ -64,16 +60,14 @@ def print_help(tickers: List[str]):
         print("")
         print("Optimization:")
         print("")
-        print("   Property weighted:")
-        print("       equal_weight   equally weighted portfolio")
-        print("       mkt_cap        marketcap weighted portfolio")
-        print("       div_yield      dividend weighted portfolio\n")
-        print("   Mean Variance Optimization :")
-        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("   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")
 
@@ -105,13 +99,6 @@ def call_quit(self, _):
         """Process Quit command - quit the program"""
         return True
 
-    def call_ca(self, _):
-
-        return ca_controller.menu(pd.DataFrame(), self.ca_ticker, "", "1440min")
-
-    def call_scr(self, _):
-        return screener_controller.menu()
-
     def call_add(self, other_args: List[str]):
         self.add_stocks(self, other_args)
 
@@ -173,7 +160,6 @@ def call_yolo(self, _):
 
     @staticmethod
     def add_stocks(self, other_args: List[str]):
-
         """ Add ticker to current list for optimization"""
         parser = argparse.ArgumentParser(
             add_help=False,
@@ -210,41 +196,6 @@ def add_stocks(self, other_args: List[str]):
 
         print("")
 
-    @classmethod
-    def from_ca_menu(cls, ticker: str, similar: List[str]):
-        return cls(set([ticker] + similar))
-
-
-def menu_from_ca(ticker: str, similar: List[str]):
-    """Portfolio Optimization Menu from ca menu that allows for jumping between"""
-    po_controller = PortfolioOptimization.from_ca_menu(ticker, similar)
-    po_controller.ca_ticker = ticker
-    po_controller.ca_similar = similar
-    po_controller.call_help([ticker] + similar)
-
-    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
-
 
 def menu(tickers: List[str]):
     """Portfolio Optimization Menu"""

From 1e59b48557d2e7341fe7b956212693ffa339f3cf Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 17:20:07 -0400
Subject: [PATCH 38/64] rename_po/ update dependencies

---
 .../comparison_analysis/ca_controller.py      |  2 +-
 ...ization_controller.py => po_controller.py} |  6 ++--
 .../portfolio_optimization/port_opt_api.py    | 36 +++++++++++++++----
 terminal.py                                   |  4 +--
 4 files changed, 35 insertions(+), 13 deletions(-)
 rename gamestonk_terminal/portfolio_optimization/{port_optimization_controller.py => po_controller.py} (98%)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 4d8b7f33670a..bbdc02f9bd64 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -16,7 +16,7 @@
 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 (
-    port_optimization_controller as po_controller,
+    po_controller as po_controller,
 )
 from gamestonk_terminal.menu import session
 from prompt_toolkit.completion import NestedCompleter
diff --git a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py
similarity index 98%
rename from gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
rename to gamestonk_terminal/portfolio_optimization/po_controller.py
index 18e04383afdd..a5d27134ba6b 100644
--- a/gamestonk_terminal/portfolio_optimization/port_optimization_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/po_controller.py
@@ -34,14 +34,14 @@ class PortfolioOptimization:
         "yolo",
     ]
 
-    def __init__(self, tickers: List[str] = None):
+    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 = set(tickers)
+        self.tickers = list(set(tickers))
         # These will allow the ca menu to be re-access
         self.ca_ticker = None
         self.ca_similar = None
@@ -103,7 +103,7 @@ def call_add(self, other_args: List[str]):
         self.add_stocks(self, other_args)
 
     def call_select(self, other_args: List[str]):
-        self.tickers = set([])
+        self.tickers = []
         self.add_stocks(self, other_args)
 
     def call_equal_weight(self, other_args: List[str]):
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 059affe85ebc..1653ebdf1511 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -33,7 +33,7 @@
 ]
 
 
-def equal_weight(list_of_stocks: List[str], _):
+def equal_weight(list_of_stocks: List[str], other_args: List[str]):
     """
     Equally weighted portfolio, where weight = 1/# of stocks
 
@@ -48,12 +48,36 @@ def equal_weight(list_of_stocks: List[str], _):
         Dictionary of weights where keys are the tickers
 
     """
-    weights = {}
-    n_stocks = len(list_of_stocks)
-    for stock in list_of_stocks:
-        weights[stock] = round(1 / n_stocks, 5)
+    parser = argparse.ArgumentParser(
+        add_help=False,
+        prog="equal_weight",
+        description="Return equally weighted portfolio holdings",
+    )
 
-    return weights
+    parser.add_argument(
+        "-v",
+        "--value",
+        default=1,
+        type=float,
+        dest="value",
+        help="Portfolio amount to determine amount spent on each",
+    )
+    try:
+        ns_parser = parse_known_args_and_warn(parser, other_args)
+        if not ns_parser:
+            return
+        weights = {}
+        values = {}
+        n_stocks = len(list_of_stocks)
+        for stock in list_of_stocks:
+            weights[stock] = round(1 / n_stocks, 5)
+            values[stock] = ns_parser.value * round(1 / n_stocks, 5)
+
+        return values
+
+    except Exception as e:
+        print(e)
+        print("")
 
 
 def property_weighting(list_of_stocks: List[str], property_type: str, _):
diff --git a/terminal.py b/terminal.py
index b47ba3570265..6b19c5011510 100644
--- a/terminal.py
+++ b/terminal.py
@@ -31,9 +31,7 @@
 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 (
-    port_optimization_controller as po_controller,
-)
+from gamestonk_terminal.portfolio_optimization import po_controller
 
 # import warnings
 # warnings.simplefilter("always")

From 1fbd0152bde2c76d73a08b5f8180e7ac66a94c88 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 17:20:28 -0400
Subject: [PATCH 39/64] rename_po/ update dependencies

---
 gamestonk_terminal/comparison_analysis/ca_controller.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index bbdc02f9bd64..b06ed049c46a 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -15,9 +15,7 @@
 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 as po_controller,
-)
+from gamestonk_terminal.portfolio_optimization import po_controller
 from gamestonk_terminal.menu import session
 from prompt_toolkit.completion import NestedCompleter
 

From 6dd25ef6081c68bb0b45848a017b6f5179b87213 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 17:34:16 -0400
Subject: [PATCH 40/64] updated some functions to be argparsed

---
 .../portfolio_optimization/po_controller.py   | 20 +++++-----
 .../portfolio_optimization/port_opt_api.py    | 39 +++++++++++++++----
 .../portfolio_optimization/port_opt_helper.py | 13 +++++++
 3 files changed, 55 insertions(+), 17 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py
index a5d27134ba6b..3bd0442e1efc 100644
--- a/gamestonk_terminal/portfolio_optimization/po_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/po_controller.py
@@ -11,6 +11,7 @@
 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 port_opt_api as po_api
+from gamestonk_terminal.portfolio_optimization.port_opt_helper import display_weights
 from gamestonk_terminal.comparison_analysis import ca_controller
 from gamestonk_terminal.screener import screener_controller
 
@@ -109,43 +110,43 @@ def call_select(self, other_args: List[str]):
     def call_equal_weight(self, other_args: List[str]):
         weights = po_api.equal_weight(self.tickers, other_args)
         print("Optimal Weights for Equal Weighting:")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_mkt_cap(self, other_args: List[str]):
         weights = po_api.property_weighting(self.tickers, "marketCap", other_args)
         print("Market Cap Weighting Weights:")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_div_yield(self, other_args: List[str]):
         weights = po_api.property_weighting(self.tickers, "dividendYield", other_args)
         print("Dividend Weighed Weights:")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_max_sharpe(self, other_args: List[str]):
         weights = po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
         print("Maximum Sharpe Weights:")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_min_vol(self, other_args: List[str]):
         weights = po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
         print("Minimum volatility Weights:")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_eff_risk(self, other_args: List[str]):
         weights = po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
         print("Weights for max returns at risk level")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_eff_ret(self, other_args: List[str]):
         weights = po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
         print("Weights for min risk at target returns")
-        print(weights)
+        display_weights(weights)
         print("")
 
     def call_show_ef(self, other_args):
@@ -183,9 +184,10 @@ def add_stocks(self, other_args: List[str]):
             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:
-                self.tickers.add(ticker)
+                tickers.add(ticker)
+            self.tickers = list(tickers)
 
             print(
                 f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}"
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 1653ebdf1511..09771095b6bc 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -80,7 +80,7 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
         print("")
 
 
-def property_weighting(list_of_stocks: List[str], property_type: str, _):
+def property_weighting(list_of_stocks: List[str], property_type: str, other_args:List[str]):
     """
     Property weighted portfolio where each weight is the relative fraction.  Examples
     Parameters
@@ -96,18 +96,41 @@ def property_weighting(list_of_stocks: List[str], property_type: str, _):
     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="Portfolio amount to determine amount spent on each",
+    )
     weights = {}
     prop = {}
     prop_sum = 0
 
-    for stock in list_of_stocks:
-        stock_prop = yf.Ticker(stock).info[property_type]
-        prop[stock] = stock_prop
-        prop_sum += stock_prop
-    for k, v in prop.items():
-        weights[k] = round(v / prop_sum, 5)
+    try:
+        ns_parser = parse_known_args_and_warn(parser, other_args)
+        if not ns_parser:
+            return
+        for stock in list_of_stocks:
+            stock_prop = yf.Ticker(stock).info[property_type]
+            prop[stock] = stock_prop
+            prop_sum += stock_prop
+        for k, v in prop.items():
+            weights[k] = round(v / prop_sum, 5) * ns_parser.value
+
+        return weights
 
-    return weights
+    except Exception as e:
+        print(e)
+        print("")
+        return
 
 
 def show_ef(list_of_stocks: List[str], other_args: List[str]):
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
index c5107aa572c4..d4e54ef1769b 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
@@ -49,3 +49,16 @@ def prepare_efficient_frontier(stock_prices: pd.DataFrame):
     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
+    """
+
+    weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
+    print(weight_df)

From b5b78320208dbb1d1efff9912e544e27bd04f998 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Mon, 12 Apr 2021 23:13:03 +0100
Subject: [PATCH 41/64] Refactor prediction menu to controller, and fbprophet
 optional

---
 gamestonk_terminal/feature_flags.py           |   3 +
 .../prediction_techniques/pred_controller.py  | 207 ++++++++++++++++++
 .../prediction_techniques/pred_menu.py        | 145 ------------
 terminal.py                                   |   6 +-
 4 files changed, 213 insertions(+), 148 deletions(-)
 create mode 100644 gamestonk_terminal/prediction_techniques/pred_controller.py
 delete mode 100644 gamestonk_terminal/prediction_techniques/pred_menu.py

diff --git a/gamestonk_terminal/feature_flags.py b/gamestonk_terminal/feature_flags.py
index 3eaebcd2bb23..5c4910a1ab2e 100644
--- a/gamestonk_terminal/feature_flags.py
+++ b/gamestonk_terminal/feature_flags.py
@@ -16,6 +16,9 @@
 # Enable Prediction features
 ENABLE_PREDICT = strtobool(os.getenv("GTFF_ENABLE_PREDICT", "False"))
 
+# Enable FB Prophet
+ENABLE_FBPROPHET = strtobool(os.getenv("GTFF_ENABLE_FBPROPHET", "False"))
+
 # Enable plot autoscaling
 USE_PLOT_AUTOSCALING = strtobool(os.getenv("GTFF_USE_PLOT_AUTOSCALING", "False"))
 
diff --git a/gamestonk_terminal/prediction_techniques/pred_controller.py b/gamestonk_terminal/prediction_techniques/pred_controller.py
new file mode 100644
index 000000000000..8d2bdb4b2be9
--- /dev/null
+++ b/gamestonk_terminal/prediction_techniques/pred_controller.py
@@ -0,0 +1,207 @@
+import argparse
+from typing import List
+import pandas as pd
+from datetime import datetime
+import matplotlib.pyplot as plt
+from gamestonk_terminal import feature_flags as gtff
+from gamestonk_terminal.helper_funcs import get_flair
+from gamestonk_terminal.menu import session
+from gamestonk_terminal.prediction_techniques import (
+    arima,
+    ets,
+    knn,
+    neural_networks,
+    regression,
+    sma,
+)
+from prompt_toolkit.completion import NestedCompleter
+
+
+class PredictionTechniquesController:
+    """Prediction Techniques Controller class"""
+
+    # Command choices
+    CHOICES = [
+        "help",
+        "q",
+        "quit",
+        "sma",
+        "ets",
+        "knn",
+        "linear",
+        "quadratic",
+        "cubic",
+        "regression",
+        "arima",
+        "mlp",
+        "rnn",
+        "lstm",
+    ]
+
+    if gtff.ENABLE_FBPROPHET:
+        CHOICES.append("prophet")
+
+    def __init__(
+        self,
+        stock: pd.DataFrame,
+        ticker: str,
+        start: datetime,
+        interval: str,
+    ):
+        """Constructor"""
+        self.stock = stock
+        self.ticker = ticker
+        self.start = start
+        self.interval = interval
+        self.pred_parser = argparse.ArgumentParser(add_help=False, prog="pred")
+        self.pred_parser.add_argument(
+            "cmd",
+            choices=self.CHOICES,
+        )
+
+    @staticmethod
+    def print_help(self):
+        """ Print help """
+
+        s_intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"]
+
+        if self.start:
+            print(
+                f"\n{s_intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})"
+            )
+        else:
+            print(f"\n{s_intraday} Stock: {self.ticker}")
+
+        print("\nPrediction Techniques:")
+        print("   help        show this prediction techniques menu again")
+        print("   q           quit this menu, and shows back to main menu")
+        print("   quit        quit to abandon program")
+        print("")
+        print("   sma         simple moving average")
+        print("   ets         exponential smoothing (e.g. Holt-Winters)")
+        print("   knn         k-Nearest Neighbors")
+        print("   linear      linear regression (polynomial 1)")
+        print("   quadratic   quadratic regression (polynomial 2)")
+        print("   cubic       cubic regression (polynomial 3)")
+        print("   regression  regression (other polynomial)")
+        print("   arima       autoregressive integrated moving average")
+        print("   mlp         MultiLayer Perceptron")
+        print("   rnn         Recurrent Neural Network")
+        print("   lstm        Long-Short Term Memory")
+        if gtff.ENABLE_FBPROPHET:
+            print("   prophet     Facebook's prophet prediction")
+        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.pred_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)
+
+    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_sma(self, other_args: List[str]):
+        """Process sma command"""
+        sma.simple_moving_average(other_args, self.ticker, self.stock)
+
+    def call_ets(self, other_args: List[str]):
+        """Process ets command"""
+        ets.exponential_smoothing(other_args, self.ticker, self.stock)
+
+    def call_knn(self, other_args: List[str]):
+        """Process knn command"""
+        knn.k_nearest_neighbors(other_args, self.ticker, self.stock)
+
+    def call_linear(self, other_args: List[str]):
+        """Process linear command"""
+        regression.regression(other_args, self.ticker, self.stock, regression.LINEAR)
+
+    def call_quadratic(self, other_args: List[str]):
+        """Process quadratic command"""
+        regression.regression(other_args, self.ticker, self.stock, regression.QUADRATIC)
+
+    def call_cubic(self, other_args: List[str]):
+        """Process cubic command"""
+        regression.regression(other_args, self.ticker, self.stock, regression.CUBIC)
+
+    def call_regression(self, other_args: List[str]):
+        """Process regression command"""
+        regression.regression(
+            other_args, self.ticker, self.stock, regression.USER_INPUT
+        )
+
+    def call_arima(self, other_args: List[str]):
+        """Process arima command"""
+        arima.arima(other_args, self.ticker, self.stock)
+
+    def call_mlp(self, other_args: List[str]):
+        """Process mlp command"""
+        neural_networks.mlp(other_args, self.ticker, self.stock)
+
+    def call_rnn(self, other_args: List[str]):
+        """Process rnn command"""
+        neural_networks.rnn(other_args, self.ticker, self.stock)
+
+    def call_lstm(self, other_args: List[str]):
+        """Process lstm command"""
+        neural_networks.lstm(other_args, self.ticker, self.stock)
+
+    if gtff.ENABLE_FBPROPHET:
+
+        def call_prophet(self, other_args: List[str]):
+            """Process prophet command"""
+            from gamestonk_terminal.prediction_techniques import fbprophet
+
+            fbprophet.fbprophet(other_args, self.ticker, self.stock)
+
+
+def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
+    """Comparison Analysis Menu"""
+
+    pred_controller = PredictionTechniquesController(stock, ticker, start, interval)
+    pred_controller.call_help(None)
+
+    while True:
+        # Get input command from user
+        if session and gtff.USE_PROMPT_TOOLKIT:
+            completer = NestedCompleter.from_nested_dict(
+                {c: None for c in pred_controller.CHOICES}
+            )
+            an_input = session.prompt(
+                f"{get_flair()} (pred)> ",
+                completer=completer,
+            )
+        else:
+            an_input = input(f"{get_flair()} (pred)> ")
+
+        try:
+            plt.close("all")
+
+            process_input = pred_controller.switch(an_input)
+
+            if process_input is not None:
+                return process_input
+
+        except SystemExit:
+            print("The command selected doesn't exist\n")
+            continue
diff --git a/gamestonk_terminal/prediction_techniques/pred_menu.py b/gamestonk_terminal/prediction_techniques/pred_menu.py
deleted file mode 100644
index 31460d87ae78..000000000000
--- a/gamestonk_terminal/prediction_techniques/pred_menu.py
+++ /dev/null
@@ -1,145 +0,0 @@
-import argparse
-
-import matplotlib.pyplot as plt
-from gamestonk_terminal import feature_flags as gtff
-from gamestonk_terminal.helper_funcs import get_flair
-from gamestonk_terminal.menu import session
-from gamestonk_terminal.prediction_techniques import (
-    arima,
-    ets,
-    fbprophet,
-    knn,
-    neural_networks,
-    regression,
-    sma,
-)
-from prompt_toolkit.completion import NestedCompleter
-
-
-def print_prediction(s_ticker, s_start, s_interval):
-    """ Print help """
-
-    s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"]
-
-    if s_start:
-        print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})")
-    else:
-        print(f"\n{s_intraday} Stock: {s_ticker}")
-
-    print("\nPrediction Techniques:")
-    print("   help        show this prediction techniques menu again")
-    print("   q           quit this menu, and shows back to main menu")
-    print("   quit        quit to abandon program")
-    print("")
-    print("   sma         simple moving average")
-    print("   ets         exponential smoothing (e.g. Holt-Winters)")
-    print("   knn         k-Nearest Neighbors")
-    print("   linear      linear regression (polynomial 1)")
-    print("   quadratic   quadratic regression (polynomial 2)")
-    print("   cubic       cubic regression (polynomial 3)")
-    print("   regression  regression (other polynomial)")
-    print("   arima       autoregressive integrated moving average")
-    print("   prophet     Facebook's prophet prediction")
-    print("   mlp         MultiLayer Perceptron")
-    print("   rnn         Recurrent Neural Network")
-    print("   lstm        Long-Short Term Memory")
-    print("")
-
-
-def pred_menu(df_stock, s_ticker, s_start, s_interval):
-
-    # Add list of arguments that the prediction techniques parser accepts
-    pred_parser = argparse.ArgumentParser(prog="pred", add_help=False)
-    choices = [
-        "help",
-        "q",
-        "quit",
-        "sma",
-        "ets",
-        "knn",
-        "linear",
-        "quadratic",
-        "cubic",
-        "regression",
-        "arima",
-        "prophet",
-        "mlp",
-        "rnn",
-        "lstm",
-    ]
-    pred_parser.add_argument("cmd", choices=choices)
-    completer = NestedCompleter.from_nested_dict({c: None for c in choices})
-
-    print_prediction(s_ticker, s_start, s_interval)
-
-    # Loop forever and ever
-    while True:
-        # Get input command from user
-        if session and gtff.USE_PROMPT_TOOLKIT:
-            as_input = session.prompt(
-                f"{get_flair()} (pred)> ",
-                completer=completer,
-            )
-        else:
-            as_input = input(f"{get_flair()} (pred)> ")
-
-        # Images are non blocking - allows to close them if we type other command
-        plt.close("all")
-
-        # Parse prediction techniques command of the list of possible commands
-        try:
-            (ns_known_args, l_args) = pred_parser.parse_known_args(as_input.split())
-
-        except SystemExit:
-            print("The command selected doesn't exist\n")
-            continue
-
-        if ns_known_args.cmd == "help":
-            print_prediction(s_ticker, s_start, s_interval)
-
-        elif ns_known_args.cmd == "q":
-            # Just leave the FA menu
-            return False
-
-        elif ns_known_args.cmd == "quit":
-            # Abandon the program
-            return True
-
-        elif ns_known_args.cmd == "sma":
-            sma.simple_moving_average(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "ets":
-            ets.exponential_smoothing(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "knn":
-            knn.k_nearest_neighbors(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "linear":
-            regression.regression(l_args, s_ticker, df_stock, regression.LINEAR)
-
-        elif ns_known_args.cmd == "quadratic":
-            regression.regression(l_args, s_ticker, df_stock, regression.QUADRATIC)
-
-        elif ns_known_args.cmd == "cubic":
-            regression.regression(l_args, s_ticker, df_stock, regression.CUBIC)
-
-        elif ns_known_args.cmd == "regression":
-            regression.regression(l_args, s_ticker, df_stock, regression.USER_INPUT)
-
-        elif ns_known_args.cmd == "arima":
-            arima.arima(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "prophet":
-            fbprophet.fbprophet(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "mlp":
-            neural_networks.mlp(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "rnn":
-            neural_networks.rnn(l_args, s_ticker, df_stock)
-
-        elif ns_known_args.cmd == "lstm":
-            neural_networks.lstm(l_args, s_ticker, df_stock)
-
-        else:
-            print("Command not recognized!")
diff --git a/terminal.py b/terminal.py
index 91fa02c2e94d..bb332052050d 100644
--- a/terminal.py
+++ b/terminal.py
@@ -252,7 +252,7 @@ def main():
 
             try:
                 # pylint: disable=import-outside-toplevel
-                from gamestonk_terminal.prediction_techniques import pred_menu as pm
+                from gamestonk_terminal.prediction_techniques import pred_controller
             except ModuleNotFoundError as e:
                 print("One of the optional packages seems to be missing")
                 print("Optional packages need to be installed")
@@ -265,7 +265,7 @@ def main():
                 continue
 
             if s_interval == "1440min":
-                b_quit = pm.pred_menu(df_stock, s_ticker, s_start, s_interval)
+                b_quit = pred_controller.menu(df_stock, 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
@@ -282,7 +282,7 @@ def main():
                     # pylint: disable=no-member
                     df_stock_pred = df_stock_pred.sort_index(ascending=True)
                     df_stock_pred = df_stock_pred[s_start:]
-                    b_quit = pm.pred_menu(
+                    b_quit = pred_controller.menu(
                         df_stock_pred, s_ticker, s_start, s_interval="1440min"
                     )
                 except Exception as e:

From 7087fc8a0c5fba78d8e76ac1729d6561d4b69090 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Mon, 12 Apr 2021 22:12:21 -0400
Subject: [PATCH 42/64] Added pie chart options!

---
 .../portfolio_optimization/po_controller.py   |  3 --
 .../portfolio_optimization/port_opt_api.py    | 53 ++++++++++++++++--
 .../portfolio_optimization/port_opt_helper.py | 54 ++++++++++++++++++-
 3 files changed, 102 insertions(+), 8 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py
index 3bd0442e1efc..cda42f940cfc 100644
--- a/gamestonk_terminal/portfolio_optimization/po_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/po_controller.py
@@ -5,15 +5,12 @@
 import argparse
 from typing import List
 import matplotlib.pyplot as plt
-import pandas as pd
 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 port_opt_api as po_api
 from gamestonk_terminal.portfolio_optimization.port_opt_helper import display_weights
-from gamestonk_terminal.comparison_analysis import ca_controller
-from gamestonk_terminal.screener import screener_controller
 
 
 class PortfolioOptimization:
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 09771095b6bc..3a120ec8eedb 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -14,6 +14,7 @@
 from gamestonk_terminal.portfolio_optimization.port_opt_helper import (
     process_stocks,
     prepare_efficient_frontier,
+    pie_chart_weights,
 )
 from gamestonk_terminal.config_plot import PLOT_DPI
 from gamestonk_terminal import feature_flags as gtff
@@ -62,6 +63,13 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
         dest="value",
         help="Portfolio amount to determine amount spent on each",
     )
+    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:
@@ -72,7 +80,8 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
         for stock in list_of_stocks:
             weights[stock] = round(1 / n_stocks, 5)
             values[stock] = ns_parser.value * round(1 / n_stocks, 5)
-
+        if ns_parser.pie:
+            pie_chart_weights(values)
         return values
 
     except Exception as e:
@@ -80,7 +89,9 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
         print("")
 
 
-def property_weighting(list_of_stocks: List[str], property_type: str, other_args:List[str]):
+def property_weighting(
+    list_of_stocks: List[str], property_type: str, other_args: List[str]
+):
     """
     Property weighted portfolio where each weight is the relative fraction.  Examples
     Parameters
@@ -110,6 +121,13 @@ def property_weighting(list_of_stocks: List[str], property_type: str, other_args
         dest="value",
         help="Portfolio amount to determine amount spent on each",
     )
+    parser.add_argument(
+        "--pie",
+        action="store_true",
+        dest="pie",
+        default=False,
+        help="Display a pie chart for weights",
+    )
     weights = {}
     prop = {}
     prop_sum = 0
@@ -124,7 +142,8 @@ def property_weighting(list_of_stocks: List[str], property_type: str, other_args
             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)
         return weights
 
     except Exception as e:
@@ -147,6 +166,7 @@ def show_ef(list_of_stocks: List[str], other_args: List[str]):
     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:
@@ -215,6 +235,21 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
         choices=period_choices,
     )
 
+    parser.add_argument(
+        "-v",
+        "--value",
+        dest="value",
+        help="Portfolio amount to determine amount spent on each",
+        type=float,
+        default=1.0,
+    )
+    parser.add_argument(
+        "--pie",
+        action="store_true",
+        dest="pie",
+        default=False,
+        help="Display a pie chart for weights",
+    )
     if port_type == "max_sharpe":
         try:
             ns_parser = parse_known_args_and_warn(parser, other_args)
@@ -225,6 +260,8 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             ef = prepare_efficient_frontier(stock_prices)
             ef_sharpe = dict(ef.max_sharpe())
             weights = {key: round(value, 5) for key, value in ef_sharpe.items()}
+            if ns_parser.pie:
+                pie_chart_weights(weights)
             print("")
             ef.portfolio_performance(verbose=True)
             print("")
@@ -244,6 +281,8 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             ef = prepare_efficient_frontier(stock_prices)
             ef_min_vol = dict(ef.min_volatility())
             weights = {key: round(value, 5) for key, value in ef_min_vol.items()}
+            if ns_parser.pie:
+                pie_chart_weights(weights)
             print("")
             ef.portfolio_performance(verbose=True)
             print("")
@@ -261,11 +300,14 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             ns_parser = parse_known_args_and_warn(parser, other_args)
             if not ns_parser:
                 return
-
+            if len(list_of_stocks) == 0:
+                return {}
             stock_prices = process_stocks(list_of_stocks, ns_parser.period)
             ef = prepare_efficient_frontier(stock_prices)
             ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
             weights = {key: round(value, 5) for key, value in ef_eff_risk.items()}
+            if ns_parser.pie:
+                pie_chart_weights(weights)
             print("")
             ef.portfolio_performance(verbose=True)
             print("")
@@ -288,10 +330,13 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             ef = prepare_efficient_frontier(stock_prices)
             ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
             weights = {key: round(value, 5) for key, value in ef_eff_risk.items()}
+            if ns_parser.pie:
+                pie_chart_weights(weights)
             print("")
             ef.portfolio_performance(verbose=True)
             print("")
             return weights
+
         except Exception as e:
             print(e)
             print("")
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
index d4e54ef1769b..f8695de0bfab 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
@@ -2,11 +2,16 @@
 __docformat__ = "numpy"
 
 from typing import List
+import math
 import pandas as pd
+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
 
 
 def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
@@ -59,6 +64,53 @@ def display_weights(weights: dict):
     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"])
     print(weight_df)
+
+
+def pie_chart_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
+    """
+
+    def pie_label(label_input):
+        """Function for autopct"""
+        return f"{label_input:.2f}"
+
+    if not weights:
+        return
+    stocks = list(weights.keys())
+    sizes = list(weights.values())
+    fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
+    if math.isclose(sum(sizes), 1, rel_tol=0.1):
+        wedges, texts, autotexts = ax.pie(
+            sizes, labels=stocks, autopct="%1.1f%%", textprops=dict(color="w")
+        )
+    else:
+        wedges, texts, autotexts = ax.pie(
+            sizes, labels=stocks, autopct="", textprops=dict(color="w")
+        )
+        for i, a in enumerate(autotexts):
+            a.set_text("{}".format(sizes[i]))
+
+    ax.axis("equal")
+    ax.legend(
+        wedges,
+        stocks,
+        title="Stocks",
+        loc="center left",
+        bbox_to_anchor=(0.85, 0, 0.5, 1),
+    )
+
+    plt.setp(autotexts, size=8, weight="bold")
+    ax.set_title("Portfolio Holdings")
+    if gtff.USE_ION:
+        plt.ion()
+    plt.show()
+    print("")

From 85e19c1c7eab1dbbdb3c6a841b96dbf064cc10f7 Mon Sep 17 00:00:00 2001
From: Theodore Aptekarev <aptekarev@gmail.com>
Date: Tue, 13 Apr 2021 13:20:05 +0300
Subject: [PATCH 43/64] Randomize similar companies before selecting 10 for
 analysis This is for the case when more than 10 similar companies are
 available. This allows getting better representation compared to always
 getting the first 10 similar companies from the list that's ordered
 alphabetically

---
 gamestonk_terminal/comparison_analysis/ca_controller.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 67b91f8b4dac..8ea557067e61 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -3,6 +3,7 @@
 
 import argparse
 import requests
+import random
 from typing import List
 from datetime import datetime
 import pandas as pd
@@ -172,9 +173,11 @@ def get_similar_companies(self, other_args: List[str]):
 
             if len(self.similar) > 10:
                 print(
-                    "\nThe limit of stocks to compare with are 10. Hence, the similar stocks list will be:"
+                    "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed.",
+                    "\nThe selected list will be:"
                 )
-                self.similar = self.similar[:10]
+                random.shuffle(self.similar)
+                self.similar = sorted(self.similar[:10])
                 print(", ".join(self.similar))
 
         except Exception as e:

From f22d07f2905b7b8d4aa7140af5c88d3f0a02b409 Mon Sep 17 00:00:00 2001
From: Nicolas Toscano <nicolas.toscano@gmail.com>
Date: Tue, 13 Apr 2021 05:25:39 -0700
Subject: [PATCH 44/64] fixed some typos

---
 gamestonk_terminal/behavioural_analysis/google_view.py | 2 +-
 notebooks/templates/due_diligence.ipynb                | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/behavioural_analysis/google_view.py b/gamestonk_terminal/behavioural_analysis/google_view.py
index 9b85d7a2dc14..9047f75e59c1 100644
--- a/gamestonk_terminal/behavioural_analysis/google_view.py
+++ b/gamestonk_terminal/behavioural_analysis/google_view.py
@@ -99,7 +99,7 @@ def regions(l_args, s_ticker):
         plt.bar(df_interest_region.index, df_interest_region[s_ticker], width=0.8)
         plt.grid(b=True, which="major", color="#666666", linestyle="-")
         plt.ylabel("Interest [%]")
-        plt.xlabel("Time")
+        plt.xlabel("Region")
         plt.show()
         print("")
 
diff --git a/notebooks/templates/due_diligence.ipynb b/notebooks/templates/due_diligence.ipynb
index cbbdc2353983..09f4d95510f7 100644
--- a/notebooks/templates/due_diligence.ipynb
+++ b/notebooks/templates/due_diligence.ipynb
@@ -68,7 +68,7 @@
    "outputs": [],
    "source": [
     "from gamestonk_terminal.technical_analysis import trendline_api as trend\n",
-    "from gamestonk_terminal.due_diligence import finviz_api as finviz"
+    "from gamestonk_terminal.due_diligence import finviz_view as finviz"
    ]
   },
   {

From bcd75588d11f61bbe30a272b02bd59ca526d4fac Mon Sep 17 00:00:00 2001
From: Theodore Aptekarev <aptekarev@gmail.com>
Date: Tue, 13 Apr 2021 15:45:05 +0300
Subject: [PATCH 45/64] Fix linter warnings

---
 gamestonk_terminal/comparison_analysis/ca_controller.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index 8ea557067e61..3214c4c6b762 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -174,7 +174,7 @@ def get_similar_companies(self, other_args: List[str]):
             if len(self.similar) > 10:
                 print(
                     "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed.",
-                    "\nThe selected list will be:"
+                    "\nThe selected list will be:",
                 )
                 random.shuffle(self.similar)
                 self.similar = sorted(self.similar[:10])

From 535e89f8b4fd07841d1b0ebcd4c08ecba475f77d Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 10:08:18 -0400
Subject: [PATCH 46/64] help out linter

---
 .../portfolio_optimization/port_opt_helper.py               | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
index f8695de0bfab..407e20237c0f 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
@@ -72,17 +72,13 @@ def display_weights(weights: dict):
 
 def pie_chart_weights(weights: dict):
     """
-    Print weights in a nice format
+    Show a pie chart of holdings
     Parameters
     ----------
     weights: dict
         weights to display.  Keys are stocks.  Values are either weights or values if -v specified
     """
 
-    def pie_label(label_input):
-        """Function for autopct"""
-        return f"{label_input:.2f}"
-
     if not weights:
         return
     stocks = list(weights.keys())

From ac27fe2da76d4ebecb7e5f45eba94c729e773d04 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 10:51:37 -0400
Subject: [PATCH 47/64] more linting

---
 .../portfolio_optimization/port_opt_helper.py             | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
index 407e20237c0f..743f44dfcb65 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
@@ -31,7 +31,6 @@ def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFra
     """
     stock_prices = yf.download(list_of_stocks, period=period, group_by="ticker")
     stock_closes = pd.DataFrame(index=stock_prices.index)
-    # process df
     for stock in list_of_stocks:
         stock_closes[stock] = stock_prices[stock]["Adj Close"]
     return stock_closes
@@ -81,15 +80,16 @@ def pie_chart_weights(weights: dict):
 
     if not weights:
         return
+
     stocks = list(weights.keys())
     sizes = list(weights.values())
-    fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
+    _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
     if math.isclose(sum(sizes), 1, rel_tol=0.1):
-        wedges, texts, autotexts = ax.pie(
+        wedges, _, autotexts = ax.pie(
             sizes, labels=stocks, autopct="%1.1f%%", textprops=dict(color="w")
         )
     else:
-        wedges, texts, autotexts = ax.pie(
+        wedges, _, autotexts = ax.pie(
             sizes, labels=stocks, autopct="", textprops=dict(color="w")
         )
         for i, a in enumerate(autotexts):

From 67028bd6e25e0bf2d5adcae0e05deb993a7d31a7 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 11:13:36 -0400
Subject: [PATCH 48/64] finally figured out I needed to upgrade pyupgrade

---
 gamestonk_terminal/portfolio_optimization/port_opt_helper.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
index 743f44dfcb65..809f99ffc12d 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
@@ -93,7 +93,7 @@ def pie_chart_weights(weights: dict):
             sizes, labels=stocks, autopct="", textprops=dict(color="w")
         )
         for i, a in enumerate(autotexts):
-            a.set_text("{}".format(sizes[i]))
+            a.set_text(f"{sizes[i]}")
 
     ax.axis("equal")
     ax.legend(

From 204140ab8070f19e611c58b0e02eec09f3018c68 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 12:32:26 -0400
Subject: [PATCH 49/64] update readme with updated flags

---
 .../portfolio_optimization/README.md          | 38 +++++++++++++------
 1 file changed, 27 insertions(+), 11 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
index 6d3e96b5a094..c5fb0c4fe6f4 100644
--- a/gamestonk_terminal/portfolio_optimization/README.md
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -42,20 +42,28 @@ Once your stocks are listed, you can select one of the options.
 * market cap weighted
 * dividend yield weighted
 ### equal weights
+Returns an equally weighted portfolio where the weights are 1/number of stocks.
 ````
-equal_weight
+equal_weight [-v --value VALUE] [--pie] 
 ````
-Returns `{"Ticker" : 1/# of tickers}`
+* -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
+mkt_cap [-v --value VALUE] [--pie]
 ````
-Returns a dictionary where each weight is given as (Company Market Cap)/(Sum all included market caps)
-###dividened yield weighted
+* -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
+div_yield [-v --value VALUE] [--pie]
 ````
-Returns weights based on relative divident yield.
+* -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>
 
@@ -86,35 +94,43 @@ The sharpe ratio is defined as
 
 The usage is:
 ````
-max_sharpe [-p PERIOD] 
+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] 
+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]
+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.  5% is 0.05.
+* -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]
+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.  5% is 0.05.
+* -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 basd on their risk and returns and shows the efficient frontier.

From a7ed6d1f19f9084d33d45655277348fbafc756b8 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 13:07:34 -0400
Subject: [PATCH 50/64] fixed where value wanst being used in some calculations

---
 .../portfolio_optimization/port_opt_api.py    | 20 +++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 3a120ec8eedb..3fd0fe636e03 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -259,7 +259,10 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             stock_prices = process_stocks(list_of_stocks, period)
             ef = prepare_efficient_frontier(stock_prices)
             ef_sharpe = dict(ef.max_sharpe())
-            weights = {key: round(value, 5) for key, value in ef_sharpe.items()}
+            weights = {
+                key: ns_parser.value * round(value, 5)
+                for key, value in ef_sharpe.items()
+            }
             if ns_parser.pie:
                 pie_chart_weights(weights)
             print("")
@@ -280,7 +283,10 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             stock_prices = process_stocks(list_of_stocks, period)
             ef = prepare_efficient_frontier(stock_prices)
             ef_min_vol = dict(ef.min_volatility())
-            weights = {key: round(value, 5) for key, value in ef_min_vol.items()}
+            weights = {
+                key: ns_parser.value * round(value, 5)
+                for key, value in ef_min_vol.items()
+            }
             if ns_parser.pie:
                 pie_chart_weights(weights)
             print("")
@@ -305,7 +311,10 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             stock_prices = process_stocks(list_of_stocks, ns_parser.period)
             ef = prepare_efficient_frontier(stock_prices)
             ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
-            weights = {key: round(value, 5) for key, value in ef_eff_risk.items()}
+            weights = {
+                key: ns_parser.value * round(value, 5)
+                for key, value in ef_eff_risk.items()
+            }
             if ns_parser.pie:
                 pie_chart_weights(weights)
             print("")
@@ -329,7 +338,10 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
             stock_prices = process_stocks(list_of_stocks, ns_parser.period)
             ef = prepare_efficient_frontier(stock_prices)
             ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
-            weights = {key: round(value, 5) for key, value in ef_eff_risk.items()}
+            weights = {
+                key: ns_parser.value * round(value, 5)
+                for key, value in ef_eff_risk.items()
+            }
             if ns_parser.pie:
                 pie_chart_weights(weights)
             print("")

From 940fa9465b51728e784ef62bb2dd069ad4dc9065 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 13:23:11 -0400
Subject: [PATCH 51/64] fix bug where mkt_cap or div_yield would show error if
 not found in yfinance info

---
 gamestonk_terminal/portfolio_optimization/port_opt_api.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 3fd0fe636e03..8df994a7f83e 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -138,6 +138,8 @@ def property_weighting(
             return
         for stock in list_of_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():

From c18327dc1373205138daf001f67c4b80be2cfaf5 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 13:43:06 -0400
Subject: [PATCH 52/64] added option to jump from scr to po.  Just pass stocks
 spaced apart

---
 gamestonk_terminal/screener/README.md              | 13 ++++++++++++-
 gamestonk_terminal/screener/screener_controller.py |  7 +++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/gamestonk_terminal/screener/README.md b/gamestonk_terminal/screener/README.md
index b4f46eb644d8..6271a7684db2 100644
--- a/gamestonk_terminal/screener/README.md
+++ b/gamestonk_terminal/screener/README.md
@@ -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
+````
\ No newline at end of file
diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py
index cc34b3e80ab1..76fd2fecb566 100644
--- a/gamestonk_terminal/screener/screener_controller.py
+++ b/gamestonk_terminal/screener/screener_controller.py
@@ -14,6 +14,7 @@
 )
 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 @@ def print_help(self):
         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,9 @@ 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]):
+        return po_controller.menu(other_args)
+
 
 def menu():
     """Screener Menu"""

From c36df9bdab98ab34ff67383ad0285ab90fa59f42 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 13:45:49 -0400
Subject: [PATCH 53/64] added option to jump from scr to po.  Just pass stocks
 spaced apart + black + spelling

---
 .../portfolio_optimization/port_opt_api.py             | 10 +++++-----
 gamestonk_terminal/screener/screener_controller.py     |  1 +
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
index 8df994a7f83e..c095ef3115a9 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/port_opt_api.py
@@ -61,7 +61,7 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
         default=1,
         type=float,
         dest="value",
-        help="Portfolio amount to determine amount spent on each",
+        help="Amount to allocate to portfolio",
     )
     parser.add_argument(
         "--pie",
@@ -93,7 +93,7 @@ def property_weighting(
     list_of_stocks: List[str], property_type: str, other_args: List[str]
 ):
     """
-    Property weighted portfolio where each weight is the relative fraction.  Examples
+    Property weighted portfolio where each weight is the relative fraction.
     Parameters
     ----------
     list_of_stocks: List[str]
@@ -119,7 +119,7 @@ def property_weighting(
         default=1,
         type=float,
         dest="value",
-        help="Portfolio amount to determine amount spent on each",
+        help="Amount to allocate to portfolio",
     )
     parser.add_argument(
         "--pie",
@@ -213,7 +213,7 @@ def show_ef(list_of_stocks: List[str], other_args: List[str]):
 
 def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str]):
     """
-    Return a portfolio based on condition in port_type  Currently defaulting to 3m of historical data
+    Return a portfolio based on condition in port_type  Defaults to 3m of historical data
     Parameters
     ----------
     list_of_stocks: List[str]
@@ -241,7 +241,7 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
         "-v",
         "--value",
         dest="value",
-        help="Portfolio amount to determine amount spent on each",
+        help="Amount to allocate to portfolio",
         type=float,
         default=1.0,
     )
diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py
index 76fd2fecb566..0fe364750b06 100644
--- a/gamestonk_terminal/screener/screener_controller.py
+++ b/gamestonk_terminal/screener/screener_controller.py
@@ -222,6 +222,7 @@ def call_signals(self, other_args: List[str]):
         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)
 
 

From aeaf6b4e16c8b52bfd0b98c1ba1d055bfa003c06 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Tue, 13 Apr 2021 17:25:19 -0400
Subject: [PATCH 54/64] refactoring :)

---
 ...port_opt_helper.py => optimizer_helper.py} |  73 ++++-
 .../{port_opt_api.py => optimizer_view.py}    | 283 ++++++++----------
 .../portfolio_optimization/po_controller.py   |  39 +--
 3 files changed, 204 insertions(+), 191 deletions(-)
 rename gamestonk_terminal/portfolio_optimization/{port_opt_helper.py => optimizer_helper.py} (59%)
 rename gamestonk_terminal/portfolio_optimization/{port_opt_api.py => optimizer_view.py} (65%)

diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
similarity index 59%
rename from gamestonk_terminal/portfolio_optimization/port_opt_helper.py
rename to gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index 809f99ffc12d..1215e34316b3 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -1,6 +1,7 @@
 """ Portfolio Optimization Helper Functions"""
 __docformat__ = "numpy"
 
+import argparse
 from typing import List
 import math
 import pandas as pd
@@ -11,7 +12,9 @@
 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
+from gamestonk_terminal.helper_funcs import plot_autoscale, parse_known_args_and_warn
+
+title_maps = {}
 
 
 def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
@@ -29,7 +32,9 @@ def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFra
     stock_closes: DataFrame
         DataFrame containing daily (adjusted) close prices for each stock in list
     """
-    stock_prices = yf.download(list_of_stocks, period=period, group_by="ticker")
+    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"]
@@ -66,16 +71,19 @@ def display_weights(weights: dict):
     if not weights:
         return
     weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
-    print(weight_df)
+    weight_df["weight"] = (weight_df["value"] * 100).astype(str) + " %"
+    print(weight_df["weight"])
 
 
-def pie_chart_weights(weights: dict):
+def pie_chart_weights(weights: dict, optimizer: str):
     """
     Show a pie chart of holdings
     Parameters
     ----------
     weights: dict
         weights to display.  Keys are stocks.  Values are either weights or values if -v specified
+    optimzer: str
+        Optmization technique used
     """
 
     if not weights:
@@ -86,11 +94,15 @@ def pie_chart_weights(weights: dict):
     _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
     if math.isclose(sum(sizes), 1, rel_tol=0.1):
         wedges, _, autotexts = ax.pie(
-            sizes, labels=stocks, autopct="%1.1f%%", textprops=dict(color="w")
+            sizes,
+            labels=stocks,
+            autopct="%1.1f%%",
+            textprops=dict(color="k"),
+            labeldistance=5,
         )
     else:
         wedges, _, autotexts = ax.pie(
-            sizes, labels=stocks, autopct="", textprops=dict(color="w")
+            sizes, labels=stocks, autopct="", textprops=dict(color="k"), labeldistance=5
         )
         for i, a in enumerate(autotexts):
             a.set_text(f"{sizes[i]}")
@@ -110,3 +122,52 @@ def pie_chart_weights(weights: dict):
         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
diff --git a/gamestonk_terminal/portfolio_optimization/port_opt_api.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
similarity index 65%
rename from gamestonk_terminal/portfolio_optimization/port_opt_api.py
rename to gamestonk_terminal/portfolio_optimization/optimizer_view.py
index c095ef3115a9..bad19006ac86 100644
--- a/gamestonk_terminal/portfolio_optimization/port_opt_api.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
@@ -11,10 +11,12 @@
 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.port_opt_helper import (
+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
@@ -34,13 +36,13 @@
 ]
 
 
-def equal_weight(list_of_stocks: List[str], other_args: List[str]):
+def equal_weight(stocks: List[str], other_args: List[str]):
     """
     Equally weighted portfolio, where weight = 1/# of stocks
 
     Parameters
     ----------
-    list_of_stocks: List[str]
+    stocks: List[str]
         List of tickers to be included in optimization
 
     Returns
@@ -74,29 +76,28 @@ def equal_weight(list_of_stocks: List[str], other_args: List[str]):
         ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
-        weights = {}
         values = {}
-        n_stocks = len(list_of_stocks)
-        for stock in list_of_stocks:
-            weights[stock] = round(1 / n_stocks, 5)
+        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)
-        return values
+        if n_stocks >= 1:
+            print("Equal Weight Portfolio: ")
+            display_weights(values)
+            print("")
 
     except Exception as e:
         print(e)
         print("")
 
 
-def property_weighting(
-    list_of_stocks: List[str], property_type: str, other_args: List[str]
-):
+def property_weighting(stocks: List[str], property_type: str, other_args: List[str]):
     """
     Property weighted portfolio where each weight is the relative fraction.
     Parameters
     ----------
-    list_of_stocks: List[str]
+    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:
@@ -136,7 +137,7 @@ def property_weighting(
         ns_parser = parse_known_args_and_warn(parser, other_args)
         if not ns_parser:
             return
-        for stock in list_of_stocks:
+        for stock in stocks:
             stock_prop = yf.Ticker(stock).info[property_type]
             if stock_prop is None:
                 stock_prop = 0
@@ -144,79 +145,32 @@ def property_weighting(
             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)
-        return weights
-
-    except Exception as e:
-        print(e)
-        print("")
-        return
-
-
-def show_ef(list_of_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 {}
-        stock_prices = process_stocks(list_of_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")
+        if property_type == "marketCap":
+            print("Market Cap Weighted Portfolio: ")
 
-        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()
+        elif property_type == "dividendYield":
+            print("Dividend Yield Weighted Portfolio: ")
 
-        plt.show()
-        print("")
+        if len(stocks) >= 1:
+            display_weights(weights)
+            print("")
 
     except Exception as e:
         print(e)
         print("")
+        return
 
 
-def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str]):
+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
     ----------
-    list_of_stocks: List[str]
+    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)
@@ -226,7 +180,7 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
         Dictionary of weights where keys are the tickers.
     """
 
-    parser = argparse.ArgumentParser(add_help=False, prog="ef")
+    parser = argparse.ArgumentParser(add_help=False, prog="port_type")
 
     parser.add_argument(
         "-p",
@@ -252,109 +206,128 @@ def ef_portfolio(list_of_stocks: List[str], port_type: str, other_args: List[str
         default=False,
         help="Display a pie chart for weights",
     )
-    if port_type == "max_sharpe":
-        try:
-            ns_parser = parse_known_args_and_warn(parser, other_args)
-            if not ns_parser:
-                return {}
-            period = ns_parser.period
-            stock_prices = process_stocks(list_of_stocks, period)
-            ef = prepare_efficient_frontier(stock_prices)
+
+    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()
             }
-            if ns_parser.pie:
-                pie_chart_weights(weights)
-            print("")
-            ef.portfolio_performance(verbose=True)
-            print("")
-            return weights
-        except Exception as e:
-            print(e)
-            print("")
-            return {}
-
-    elif port_type == "min_volatility":
-        try:
-            ns_parser = parse_known_args_and_warn(parser, other_args)
-            if not ns_parser:
-                return {}
-            period = ns_parser.period
-            stock_prices = process_stocks(list_of_stocks, period)
-            ef = prepare_efficient_frontier(stock_prices)
+            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()
             }
-            if ns_parser.pie:
-                pie_chart_weights(weights)
-            print("")
-            ef.portfolio_performance(verbose=True)
-            print("")
-            return weights
+            print("Weights that minimize volatility")
+
+        elif port_type == "eff_risk":
 
-        except Exception as e:
-            print(e)
-            print("")
-            return {}
-
-    elif port_type == "eff_risk":
-
-        parser.add_argument("-r", "--risk", type=float, dest="risk_level", default=0.1)
-        try:
-            ns_parser = parse_known_args_and_warn(parser, other_args)
-            if not ns_parser:
-                return
-            if len(list_of_stocks) == 0:
-                return {}
-            stock_prices = process_stocks(list_of_stocks, ns_parser.period)
-            ef = prepare_efficient_frontier(stock_prices)
             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()
             }
-            if ns_parser.pie:
-                pie_chart_weights(weights)
-            print("")
-            ef.portfolio_performance(verbose=True)
-            print("")
-            return weights
-        except Exception as e:
-            print(e)
-            print("")
-            return {}
-
-    elif port_type == "eff_ret":
-
-        parser.add_argument(
-            "-r", "--return", type=float, dest="target_return", default=0.1
-        )
-        try:
-            ns_parser = parse_known_args_and_warn(parser, other_args)
-            if not ns_parser:
-                return
-            stock_prices = process_stocks(list_of_stocks, ns_parser.period)
-            ef = prepare_efficient_frontier(stock_prices)
+            print("Weights for maximizing returns at your risk level:")
+
+        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()
             }
-            if ns_parser.pie:
-                pie_chart_weights(weights)
-            print("")
-            ef.portfolio_performance(verbose=True)
-            print("")
-            return weights
+            print("Weights for minimizing risk at your return:")
 
-        except Exception as e:
-            print(e)
-            print("")
-            return {}
+        else:
+            raise ValueError("EF Method not found")
+
+        if ns_parser.pie:
+            pie_chart_weights(weights, port_type)
+
+        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)
 
-    else:
-        raise ValueError("EF Method not found")
+        # 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("")
diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py
index cda42f940cfc..fa515d0811be 100644
--- a/gamestonk_terminal/portfolio_optimization/po_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/po_controller.py
@@ -9,8 +9,8 @@
 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 port_opt_api as po_api
-from gamestonk_terminal.portfolio_optimization.port_opt_helper import display_weights
+from gamestonk_terminal.portfolio_optimization import optimizer_view as po_api
+from gamestonk_terminal.portfolio_optimization.optimizer_helper import display_weights
 
 
 class PortfolioOptimization:
@@ -105,46 +105,25 @@ def call_select(self, other_args: List[str]):
         self.add_stocks(self, other_args)
 
     def call_equal_weight(self, other_args: List[str]):
-        weights = po_api.equal_weight(self.tickers, other_args)
-        print("Optimal Weights for Equal Weighting:")
-        display_weights(weights)
-        print("")
+        po_api.equal_weight(self.tickers, other_args)
 
     def call_mkt_cap(self, other_args: List[str]):
-        weights = po_api.property_weighting(self.tickers, "marketCap", other_args)
-        print("Market Cap Weighting Weights:")
-        display_weights(weights)
-        print("")
+        po_api.property_weighting(self.tickers, "marketCap", other_args)
 
     def call_div_yield(self, other_args: List[str]):
-        weights = po_api.property_weighting(self.tickers, "dividendYield", other_args)
-        print("Dividend Weighed Weights:")
-        display_weights(weights)
-        print("")
+        po_api.property_weighting(self.tickers, "dividendYield", other_args)
 
     def call_max_sharpe(self, other_args: List[str]):
-        weights = po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
-        print("Maximum Sharpe Weights:")
-        display_weights(weights)
-        print("")
+        po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
 
     def call_min_vol(self, other_args: List[str]):
-        weights = po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
-        print("Minimum volatility Weights:")
-        display_weights(weights)
-        print("")
+        po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
 
     def call_eff_risk(self, other_args: List[str]):
-        weights = po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
-        print("Weights for max returns at risk level")
-        display_weights(weights)
-        print("")
+        po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
 
     def call_eff_ret(self, other_args: List[str]):
-        weights = po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
-        print("Weights for min risk at target returns")
-        display_weights(weights)
-        print("")
+        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)

From 32efa688cf5a005231ec4443cf3b527a7c2e36dc Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Wed, 14 Apr 2021 02:01:57 +0100
Subject: [PATCH 55/64] Refactor options menu

---
 gamestonk_terminal/options/op_controller.py |  43 +--
 gamestonk_terminal/options/volume.py        | 176 -------------
 gamestonk_terminal/options/volume_view.py   | 273 ++++++++++++++++++++
 terminal.py                                 |   4 +-
 4 files changed, 298 insertions(+), 198 deletions(-)
 delete mode 100644 gamestonk_terminal/options/volume.py
 create mode 100644 gamestonk_terminal/options/volume_view.py

diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py
index 9459833b1801..3600a50e2ba6 100644
--- a/gamestonk_terminal/options/op_controller.py
+++ b/gamestonk_terminal/options/op_controller.py
@@ -9,7 +9,7 @@
 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
 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"]
 
-    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",
@@ -48,7 +47,7 @@ def expiry_dates(self, other_args: List[str]):
             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 @@ def expiry_dates(self, other_args: List[str]):
             # 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,7 @@ def print_help(expiry_date):
         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("")
         return
 
@@ -121,26 +121,27 @@ def call_quit(self, _):
         """Process Quit command - quit the program."""
         return True
 
-    def call_volume(self, _):
-        """Process volume command."""
-        volume.volume_graph(
-            self.raw_data_options,
+    def call_voi(self, other_args: List[str]):
+        """Process voi command."""
+        volume_view.volume_open_interest_graph(
+            other_args,
             self.ticker,
             self.expiry_date,
-            volume_percentile_threshold=60,
+            self.last_adj_close_price,
+            self.options.calls,
+            self.options.puts,
         )
-        print("")
 
     def call_exp(self, other_args: List[str]):
         """Process exp command."""
         self.expiry_dates(self, other_args)
 
 
-def menu(ticker: str):
-    """Options info Menu."""
+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)
diff --git a/gamestonk_terminal/options/volume.py b/gamestonk_terminal/options/volume.py
deleted file mode 100644
index 3b87ac8efe76..000000000000
--- a/gamestonk_terminal/options/volume.py
+++ /dev/null
@@ -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]
diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py
new file mode 100644
index 000000000000..26db5219a277
--- /dev/null
+++ b/gamestonk_terminal/options/volume_view.py
@@ -0,0 +1,273 @@
+""" Volume view """
+__docformat__ = "numpy"
+
+from bisect import bisect_left
+import seaborn as sns
+from typing import List
+import argparse
+import pandas as pd
+import matplotlib.pyplot as plt
+import numpy as np
+import yfinance
+
+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 volume_open_interest_graph(
+    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
+    """
+    # 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"])
+
+    # 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"])
+
+    # 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
diff --git a/terminal.py b/terminal.py
index f2441ccbdab8..cfcc432bb043 100644
--- a/terminal.py
+++ b/terminal.py
@@ -231,7 +231,9 @@ def main():
                 b_quit = eda_controller.menu(df_stock, 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()

From e0e15abeba714ee285763ba006b9ccae2a6b033a Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Wed, 14 Apr 2021 02:04:14 +0100
Subject: [PATCH 56/64] remove unused import

---
 gamestonk_terminal/options/volume_view.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py
index 26db5219a277..e4aadb946a3a 100644
--- a/gamestonk_terminal/options/volume_view.py
+++ b/gamestonk_terminal/options/volume_view.py
@@ -8,7 +8,6 @@
 import pandas as pd
 import matplotlib.pyplot as plt
 import numpy as np
-import yfinance
 
 from gamestonk_terminal.helper_funcs import (
     plot_autoscale,

From ce0cc6a6588832bbac4830c963ee1519b56f4dd1 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Wed, 14 Apr 2021 02:27:22 +0100
Subject: [PATCH 57/64] add vcalls and vputs commands to options menu

---
 gamestonk_terminal/options/op_controller.py |  30 ++-
 gamestonk_terminal/options/volume_view.py   | 199 ++++++++++++++++----
 2 files changed, 190 insertions(+), 39 deletions(-)

diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py
index 3600a50e2ba6..01eb7e0df670 100644
--- a/gamestonk_terminal/options/op_controller.py
+++ b/gamestonk_terminal/options/op_controller.py
@@ -17,7 +17,7 @@ class OptionsController:
     """Options Controller class."""
 
     # Command choices
-    CHOICES = ["help", "q", "quit", "exp", "voi"]
+    CHOICES = ["help", "q", "quit", "exp", "voi", "vcalls", "vputs"]
 
     def __init__(self, ticker: str, last_adj_close_price: float):
         """Construct data."""
@@ -90,6 +90,8 @@ def print_help(expiry_date):
         print("")
         print("   exp           see/set expiry date")
         print("   voi           volume + open interest options trading plot")
+        print("   vcalls        calls volume + open interest plot")
+        print("   vputs         puts volume + open interest plot")
         print("")
         return
 
@@ -121,9 +123,13 @@ def call_quit(self, _):
         """Process Quit command - quit the program."""
         return True
 
+    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.volume_open_interest_graph(
+        volume_view.plot_volume_open_interest(
             other_args,
             self.ticker,
             self.expiry_date,
@@ -132,9 +138,23 @@ def call_voi(self, other_args: List[str]):
             self.options.puts,
         )
 
-    def call_exp(self, other_args: List[str]):
-        """Process exp command."""
-        self.expiry_dates(self, other_args)
+    def call_vcalls(self, other_args: List[str]):
+        """Process vcalls command."""
+        volume_view.plot_calls_volume_open_interest(
+            other_args,
+            self.ticker,
+            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.last_adj_close_price,
+            self.options.puts,
+        )
 
 
 def menu(ticker: str, last_adj_close_price: float):
diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py
index e4aadb946a3a..4a84471e04e8 100644
--- a/gamestonk_terminal/options/volume_view.py
+++ b/gamestonk_terminal/options/volume_view.py
@@ -18,7 +18,7 @@
 from gamestonk_terminal import feature_flags as gtff
 
 
-def volume_open_interest_graph(
+def plot_volume_open_interest(
     other_args: List[str],
     ticker: str,
     exp_date: str,
@@ -235,38 +235,169 @@ def get_calls_puts_maxpain(
     float
         Max pain
     """
-    # 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"])
-
-    # 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"])
-
-    # 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()
+    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,
+    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
+    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]
+        """,
+    )
+
+    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
+        )
+
+        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",
+        )
+        plt.bar(df_calls["strike"], df_calls["volume"], color="green")
+
+        plt.title("Calls Volume")
+        plt.legend(["Stock Price", "Open Interest", "Volume"])
+        plt.xlabel("Strike Price")
+        plt.ylabel("Volume")
+
+        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,
+    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
+    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]
+        """,
+    )
+
+    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
+        )
+
+        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",
+        )
+        plt.bar(df_puts["strike"], -df_puts["volume"], color="red")
+
+        plt.title("Puts Volume")
+        plt.legend(["Stock Price", "Open Interest", "Volume"])
+        plt.xlabel("Strike Price")
+        plt.ylabel("Volume")
+
+        if gtff.USE_ION:
+            plt.ion()
+        plt.show()
+
+        print("")
+
+    except Exception as e:
+        print(e, "\n")
+        return

From f33dddebc4b72312d716cd1e915cd27719c5917c Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Wed, 14 Apr 2021 11:56:56 -0400
Subject: [PATCH 58/64] more refactoring + edit pie chart function

---
 .../portfolio_optimization/README.md          | 35 +++++++-
 .../optimizer_helper.py                       | 90 ++++++++++++++-----
 .../portfolio_optimization/optimizer_view.py  | 14 +--
 .../portfolio_optimization/po_controller.py   |  2 +-
 4 files changed, 111 insertions(+), 30 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
index c5fb0c4fe6f4..f0f1b62fc5bc 100644
--- a/gamestonk_terminal/portfolio_optimization/README.md
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -117,7 +117,7 @@ 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.  5% is 0.05.
+* -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.
 
@@ -128,15 +128,42 @@ 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.  5% is 0.05.
+* -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 basd on their risk and returns and shows the efficient frontier.
+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.
\ No newline at end of file
+* -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`.  Ths 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 ommitted from the chart.
\ No newline at end of file
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index 1215e34316b3..e341f32ae249 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -2,9 +2,10 @@
 __docformat__ = "numpy"
 
 import argparse
-from typing import List
+from typing import List, Optional
 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
@@ -14,7 +15,15 @@
 from gamestonk_terminal import feature_flags as gtff
 from gamestonk_terminal.helper_funcs import plot_autoscale, parse_known_args_and_warn
 
-title_maps = {}
+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:
@@ -71,11 +80,24 @@ def display_weights(weights: dict):
     if not weights:
         return
     weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
-    weight_df["weight"] = (weight_df["value"] * 100).astype(str) + " %"
-    print(weight_df["weight"])
+    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):
+
+def pie_chart_weights(weights: dict, optimizer: str, value: Optional[float]):
     """
     Show a pie chart of holdings
     Parameters
@@ -83,43 +105,71 @@ def pie_chart_weights(weights: dict, optimizer: str):
     weights: dict
         weights to display.  Keys are stocks.  Values are either weights or values if -v specified
     optimzer: str
-        Optmization technique used
+        Optimization technique used for title
     """
-
+    plt.close("all")
     if not weights:
         return
 
-    stocks = list(weights.keys())
-    sizes = list(weights.values())
-    _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
+    stocks = np.array(list(weights.keys()))
+    sizes = np.array(list(weights.values()))
+
+    to_not_include = sizes == 0
+
+    stocks, sizes = stocks[to_not_include == False], sizes[to_not_include == False]
+    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)
+    ]
+
     if math.isclose(sum(sizes), 1, rel_tol=0.1):
-        wedges, _, autotexts = ax.pie(
+        wedges, _, autotexts = plt.pie(
             sizes,
             labels=stocks,
-            autopct="%1.1f%%",
+            autopct=my_autopct,
             textprops=dict(color="k"),
-            labeldistance=5,
+            explode=[s / (5 * total_size) for s in sizes],
+            normalize=True,
+            shadow=True,
         )
     else:
-        wedges, _, autotexts = ax.pie(
-            sizes, labels=stocks, autopct="", textprops=dict(color="k"), labeldistance=5
+        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):
-            a.set_text(f"{sizes[i]}")
+            if sizes[i] / total_size > 0.05:
+                a.set_text(f"{sizes[i]:.2f}")
+            else:
+                a.set_text("")
+
+    plt.axis("equal")
 
-    ax.axis("equal")
-    ax.legend(
+    plt.legend(
         wedges,
-        stocks,
+        leg_labels,
         title="Stocks",
         loc="center left",
         bbox_to_anchor=(0.85, 0, 0.5, 1),
     )
 
     plt.setp(autotexts, size=8, weight="bold")
-    ax.set_title("Portfolio Holdings")
+
+    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("")
 
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
index bad19006ac86..75f24bef38ef 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
@@ -81,7 +81,7 @@ def equal_weight(stocks: List[str], other_args: List[str]):
         for stock in stocks:
             values[stock] = ns_parser.value * round(1 / n_stocks, 5)
         if ns_parser.pie:
-            pie_chart_weights(values)
+            pie_chart_weights(values, "equal", None)
         if n_stocks >= 1:
             print("Equal Weight Portfolio: ")
             display_weights(values)
@@ -147,7 +147,7 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
             weights[k] = round(v / prop_sum, 5) * ns_parser.value
 
         if ns_parser.pie:
-            pie_chart_weights(weights)
+            pie_chart_weights(weights, property_type, None)
 
         if property_type == "marketCap":
             print("Market Cap Weighted Portfolio: ")
@@ -225,6 +225,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
                 key: ns_parser.value * round(value, 5)
                 for key, value in ef_sharpe.items()
             }
+            val = None
             print("Weights that maximize Sharpe Ratio:")
 
         elif port_type == "min_volatility":
@@ -234,6 +235,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
                 key: ns_parser.value * round(value, 5)
                 for key, value in ef_min_vol.items()
             }
+            val = None
             print("Weights that minimize volatility")
 
         elif port_type == "eff_risk":
@@ -243,7 +245,8 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
                 key: ns_parser.value * round(value, 5)
                 for key, value in ef_eff_risk.items()
             }
-            print("Weights for maximizing returns at your risk level:")
+            val = ns_parser.risk_level
+            print(f"Weights for maximizing returns at risk = {100*val:.1f} %")
 
         elif port_type == "eff_ret":
 
@@ -252,13 +255,14 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
                 key: ns_parser.value * round(value, 5)
                 for key, value in ef_eff_risk.items()
             }
-            print("Weights for minimizing risk at your return:")
+            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)
+            pie_chart_weights(weights, port_type, val)
 
         print("")
         ef.portfolio_performance(verbose=True)
diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py
index fa515d0811be..df2f8a55fa6b 100644
--- a/gamestonk_terminal/portfolio_optimization/po_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/po_controller.py
@@ -10,7 +10,6 @@
 from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
 from gamestonk_terminal.menu import session
 from gamestonk_terminal.portfolio_optimization import optimizer_view as po_api
-from gamestonk_terminal.portfolio_optimization.optimizer_helper import display_weights
 
 
 class PortfolioOptimization:
@@ -179,6 +178,7 @@ def menu(tickers: List[str]):
     """Portfolio Optimization Menu"""
     if tickers == [""]:
         tickers = []
+    plt.close("all")
     po_controller = PortfolioOptimization(tickers)
     po_controller.call_help(tickers)
 

From 0228fb17c46205857ca1a5e26cba9d327cea6758 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Wed, 14 Apr 2021 12:04:10 -0400
Subject: [PATCH 59/64] linting

---
 gamestonk_terminal/portfolio_optimization/README.md           | 4 ++--
 gamestonk_terminal/portfolio_optimization/optimizer_helper.py | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
index f0f1b62fc5bc..bf96cf776295 100644
--- a/gamestonk_terminal/portfolio_optimization/README.md
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -153,7 +153,7 @@ 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`.  Ths optimization will be (including a pie chart!).  Note we could also supply a different
+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.
 ````
@@ -166,4 +166,4 @@ 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 ommitted from the chart.
\ No newline at end of file
+Note that since `AAPL` had zero allocation, it was omitted from the chart.
\ No newline at end of file
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index e341f32ae249..750ab058262d 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -123,7 +123,7 @@ def pie_chart_weights(weights: dict, optimizer: str, value: Optional[float]):
         f"{str(a)}: {str(round(100*b/total_size,3))[:4]}%"
         for a, b in zip(stocks, sizes)
     ]
-
+    fig = plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
     if math.isclose(sum(sizes), 1, rel_tol=0.1):
         wedges, _, autotexts = plt.pie(
             sizes,

From b14fee3d38b1a4384ffc3475a734a46c4b7b5c7f Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Wed, 14 Apr 2021 12:10:41 -0400
Subject: [PATCH 60/64] more lint

---
 .../portfolio_optimization/optimizer_helper.py    | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index 750ab058262d..43568468504e 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -111,19 +111,22 @@ def pie_chart_weights(weights: dict, optimizer: str, value: Optional[float]):
     if not weights:
         return
 
-    stocks = np.array(list(weights.keys()))
-    sizes = np.array(list(weights.values()))
+    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)
 
-    to_not_include = sizes == 0
-
-    stocks, sizes = stocks[to_not_include == False], sizes[to_not_include == False]
     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)
     ]
-    fig = plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
+    plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
     if math.isclose(sum(sizes), 1, rel_tol=0.1):
         wedges, _, autotexts = plt.pie(
             sizes,

From 7fdb601ac52e1a7b7b2dde1eb524c691c8732c3a Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Wed, 14 Apr 2021 12:13:54 -0400
Subject: [PATCH 61/64] should be last lint

---
 .../portfolio_optimization/optimizer_helper.py            | 2 +-
 .../portfolio_optimization/optimizer_view.py              | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index 43568468504e..d3134829b70d 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -97,7 +97,7 @@ def my_autopct(x):
         return ""
 
 
-def pie_chart_weights(weights: dict, optimizer: str, value: Optional[float]):
+def pie_chart_weights(weights: dict, optimizer: str, value: float):
     """
     Show a pie chart of holdings
     Parameters
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
index 75f24bef38ef..2014b9b369db 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
@@ -81,7 +81,7 @@ def equal_weight(stocks: List[str], other_args: List[str]):
         for stock in stocks:
             values[stock] = ns_parser.value * round(1 / n_stocks, 5)
         if ns_parser.pie:
-            pie_chart_weights(values, "equal", None)
+            pie_chart_weights(values, "equal", 0)
         if n_stocks >= 1:
             print("Equal Weight Portfolio: ")
             display_weights(values)
@@ -147,7 +147,7 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
             weights[k] = round(v / prop_sum, 5) * ns_parser.value
 
         if ns_parser.pie:
-            pie_chart_weights(weights, property_type, None)
+            pie_chart_weights(weights, property_type, 0)
 
         if property_type == "marketCap":
             print("Market Cap Weighted Portfolio: ")
@@ -225,7 +225,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
                 key: ns_parser.value * round(value, 5)
                 for key, value in ef_sharpe.items()
             }
-            val = None
+            val = 0
             print("Weights that maximize Sharpe Ratio:")
 
         elif port_type == "min_volatility":
@@ -235,7 +235,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
                 key: ns_parser.value * round(value, 5)
                 for key, value in ef_min_vol.items()
             }
-            val = None
+            val = 0
             print("Weights that minimize volatility")
 
         elif port_type == "eff_risk":

From 41d99deda85ff4d547585882cb095b5fc1cdea22 Mon Sep 17 00:00:00 2001
From: Jamie <jmaslek11@gmail.com>
Date: Wed, 14 Apr 2021 12:16:28 -0400
Subject: [PATCH 62/64] should be last lint.....

---
 gamestonk_terminal/portfolio_optimization/optimizer_helper.py | 2 +-
 gamestonk_terminal/portfolio_optimization/optimizer_view.py   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index d3134829b70d..3ca81f396a2c 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -2,7 +2,7 @@
 __docformat__ = "numpy"
 
 import argparse
-from typing import List, Optional
+from typing import List
 import math
 import pandas as pd
 import numpy as np
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
index 2014b9b369db..e9d74a761386 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
@@ -180,7 +180,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
         Dictionary of weights where keys are the tickers.
     """
 
-    parser = argparse.ArgumentParser(add_help=False, prog="port_type")
+    parser = argparse.ArgumentParser(add_help=False, prog=port_type)
 
     parser.add_argument(
         "-p",

From 14feae913c2c76fa4744229ef7b56d4b3405a368 Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Wed, 14 Apr 2021 21:45:13 +0100
Subject: [PATCH 63/64] improve current options plots

---
 gamestonk_terminal/options/op_controller.py |  2 +
 gamestonk_terminal/options/volume_view.py   | 64 ++++++++++++++++++++-
 2 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py
index 01eb7e0df670..e373ba1f6773 100644
--- a/gamestonk_terminal/options/op_controller.py
+++ b/gamestonk_terminal/options/op_controller.py
@@ -143,6 +143,7 @@ def call_vcalls(self, other_args: List[str]):
         volume_view.plot_calls_volume_open_interest(
             other_args,
             self.ticker,
+            self.expiry_date,
             self.last_adj_close_price,
             self.options.calls,
         )
@@ -152,6 +153,7 @@ def call_vputs(self, other_args: List[str]):
         volume_view.plot_puts_volume_open_interest(
             other_args,
             self.ticker,
+            self.expiry_date,
             self.last_adj_close_price,
             self.options.puts,
         )
diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py
index 4a84471e04e8..fed8eb63c6f4 100644
--- a/gamestonk_terminal/options/volume_view.py
+++ b/gamestonk_terminal/options/volume_view.py
@@ -282,6 +282,7 @@ def get_calls_puts_maxpain(
 def plot_calls_volume_open_interest(
     other_args: List[str],
     ticker: str,
+    exp_date: str,
     last_adj_close_price: float,
     op_calls: pd.DataFrame,
 ):
@@ -293,6 +294,8 @@ def plot_calls_volume_open_interest(
         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
@@ -305,6 +308,22 @@ def plot_calls_volume_open_interest(
             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)
@@ -315,6 +334,16 @@ def plot_calls_volume_open_interest(
             op_calls, pd.DataFrame(), last_adj_close_price
         )
 
+        if ns_parser.min_sp == -1:
+            min_strike = np.percentile(df_calls["strike"], 25)
+        else:
+            min_strike = ns_parser.min_sp
+
+        if ns_parser.max_sp == -1:
+            max_strike = np.percentile(df_calls["strike"], 75)
+        else:
+            max_strike = ns_parser.max_sp
+
         plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
 
         plt.axvline(last_adj_close_price, lw=2)
@@ -325,10 +354,11 @@ def plot_calls_volume_open_interest(
         )
         plt.bar(df_calls["strike"], df_calls["volume"], color="green")
 
-        plt.title("Calls Volume")
+        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()
@@ -344,6 +374,7 @@ def plot_calls_volume_open_interest(
 def plot_puts_volume_open_interest(
     other_args: List[str],
     ticker: str,
+    exp_date: str,
     last_adj_close_price: float,
     op_puts: pd.DataFrame,
 ):
@@ -355,6 +386,8 @@ def plot_puts_volume_open_interest(
         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
@@ -367,6 +400,22 @@ def plot_puts_volume_open_interest(
             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)
@@ -377,6 +426,16 @@ def plot_puts_volume_open_interest(
             pd.DataFrame(), op_puts, last_adj_close_price
         )
 
+        if ns_parser.min_sp == -1:
+            min_strike = np.percentile(df_puts["strike"], 25)
+        else:
+            min_strike = ns_parser.min_sp
+
+        if ns_parser.max_sp == -1:
+            max_strike = np.percentile(df_puts["strike"], 75)
+        else:
+            max_strike = ns_parser.max_sp
+
         plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
 
         plt.axvline(last_adj_close_price, lw=2)
@@ -387,10 +446,11 @@ def plot_puts_volume_open_interest(
         )
         plt.bar(df_puts["strike"], -df_puts["volume"], color="red")
 
-        plt.title("Puts Volume")
+        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()

From 1b16cd18806a96813059c33ed56be53f81f1a64a Mon Sep 17 00:00:00 2001
From: didier <dro.lopes@campus.fct.unl.pt>
Date: Wed, 14 Apr 2021 23:13:43 +0100
Subject: [PATCH 64/64] improve vcalls and vputs default plots

---
 gamestonk_terminal/options/volume_view.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py
index fed8eb63c6f4..a20e4cafc540 100644
--- a/gamestonk_terminal/options/volume_view.py
+++ b/gamestonk_terminal/options/volume_view.py
@@ -335,12 +335,12 @@ def plot_calls_volume_open_interest(
         )
 
         if ns_parser.min_sp == -1:
-            min_strike = np.percentile(df_calls["strike"], 25)
+            min_strike = 0.75 * last_adj_close_price
         else:
             min_strike = ns_parser.min_sp
 
         if ns_parser.max_sp == -1:
-            max_strike = np.percentile(df_calls["strike"], 75)
+            max_strike = 1.25 * last_adj_close_price
         else:
             max_strike = ns_parser.max_sp
 
@@ -427,12 +427,12 @@ def plot_puts_volume_open_interest(
         )
 
         if ns_parser.min_sp == -1:
-            min_strike = np.percentile(df_puts["strike"], 25)
+            min_strike = 0.75 * last_adj_close_price
         else:
             min_strike = ns_parser.min_sp
 
         if ns_parser.max_sp == -1:
-            max_strike = np.percentile(df_puts["strike"], 75)
+            max_strike = 1.25 * last_adj_close_price
         else:
             max_strike = ns_parser.max_sp