diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 17ba82ef845..13c9be331d3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -26,7 +26,7 @@ jobs:
key: ${{ runner.os }}-linting
restore-keys: ${{ runner.os }}-linting
- - run: pip install bandit black codespell flake8 mypy pyupgrade safety pylint==2.12.2 black[jupyter]
+ - run: pip install bandit black codespell flake8 mypy pyupgrade safety pylint==2.12.2
- run: pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil types-setuptools
- run: bandit -x ./tests -r . || true
- run: black --check .
diff --git a/gamestonk_terminal/jupyter/dashboards/chains.ipynb b/gamestonk_terminal/jupyter/dashboards/chains.ipynb
new file mode 100644
index 00000000000..af55e079b05
--- /dev/null
+++ b/gamestonk_terminal/jupyter/dashboards/chains.ipynb
@@ -0,0 +1,179 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "e9ad60bc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import ipywidgets as widgets\n",
+ "import plotly.graph_objs as go\n",
+ "import yfinance as yf\n",
+ "from IPython.display import display"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "beb36881",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def clean_str(string):\n",
+ " new_str = \"\"\n",
+ " for letter in string:\n",
+ " if letter.isupper():\n",
+ " new_str += \" \"\n",
+ " new_str += letter\n",
+ " return new_str.title()\n",
+ "\n",
+ "def format_plotly(fig, x, y, ticker, expiry, inst):\n",
+ " fig.update_yaxes(title=clean_str(x))\n",
+ " fig.update_xaxes(title=clean_str(y))\n",
+ " title = f\"{clean_str(y)} vs. {clean_str(x)} for {ticker.upper()} {inst}s on {expiry}\"\n",
+ " fig.update_layout(\n",
+ " autosize=False,\n",
+ " width=1000,\n",
+ " height=500,\n",
+ " title={\n",
+ " \"text\": title,\n",
+ " \"y\": 0.95,\n",
+ " \"x\": 0.5,\n",
+ " \"xanchor\": \"center\",\n",
+ " \"yanchor\": \"top\",\n",
+ " },\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "f8c2f65c",
+ "metadata": {
+ "tags": [
+ "parameters"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "295abc68725748ad95afc1ffdf7825de",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "VBox(children=(HTML(value='
Option Chain Dashboard
'), VBox(children=(Text(value='', description='Tickeā¦"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "def view_chart(ticker, expiry, inst, x, y):\n",
+ " global last_ticker, last_exp\n",
+ " # set_interval_opts(interval_widget,start,end)\n",
+ " if ticker:\n",
+ " stock = yf.Ticker(ticker)\n",
+ " if ticker != last_ticker:\n",
+ " exps = stock.options\n",
+ " exps = [\"\"] + list(exps)\n",
+ " exp_w.options = exps\n",
+ " last_ticker = ticker\n",
+ "\n",
+ " if expiry:\n",
+ " chains = stock.option_chain(expiry)\n",
+ " df = chains.calls if inst == \"Call\" else chains.puts\n",
+ "\n",
+ " fig = go.Figure()\n",
+ " plot = go.Scatter(\n",
+ " x=df[x],\n",
+ " y=df[y],\n",
+ " mode=\"lines\",\n",
+ " )\n",
+ " fig.add_trace(plot)\n",
+ "\n",
+ " format_plotly(fig, x, y, ticker, expiry, inst)\n",
+ "\n",
+ " if os.environ.get(\"SERVER_SOFTWARE\", \"jupyter\").startswith(\"voila\"):\n",
+ " fig.show(config={\"showTips\": False}, renderer=\"notebook\")\n",
+ " else:\n",
+ " fig.show(config={\"showTips\": False})\n",
+ "\n",
+ "\n",
+ "last_ticker = \"\"\n",
+ "last_exp = \"\"\n",
+ "opts = [\n",
+ " \"lastTradeDate\",\n",
+ " \"strike\",\n",
+ " \"lastPrice\",\n",
+ " \"bid\",\n",
+ " \"ask\",\n",
+ " \"change\",\n",
+ " \"percentChange\",\n",
+ " \"volume\",\n",
+ " \"openInterest\",\n",
+ " \"impliedVolatility\",\n",
+ "]\n",
+ "\n",
+ "x_w = widgets.Dropdown(options=opts, value=\"strike\", description=\"X\")\n",
+ "y_w = widgets.Dropdown(options=opts, value=\"lastPrice\", description=\"Y\")\n",
+ "tickers_w = widgets.Text(description=\"Ticker\")\n",
+ "exp_w = widgets.Dropdown(description=\"Expiry\")\n",
+ "inst_w = widgets.Dropdown(options=[\"Put\", \"Call\"], value=\"Call\", description=\"Type\")\n",
+ "controls = widgets.VBox([tickers_w, exp_w, inst_w, x_w, y_w])\n",
+ "\n",
+ "stocks_view = widgets.interactive_output(\n",
+ " view_chart,\n",
+ " {\"ticker\": tickers_w, \"expiry\": exp_w, \"inst\": inst_w, \"x\": x_w, \"y\": y_w},\n",
+ ")\n",
+ "\n",
+ "title_html = \"Option Chain Dashboard
\"\n",
+ "app_contents = [\n",
+ " widgets.HTML(title_html),\n",
+ " controls,\n",
+ " stocks_view,\n",
+ "]\n",
+ "app = widgets.VBox(app_contents)\n",
+ "display(app)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a84c7390-6b43-4a25-8aa2-31aa84f52605",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.12"
+ },
+ "metadata": {
+ "interpreter": {
+ "hash": "e896a00ead8b521528d79ac9ef24990696f2b751eb283ab8e0d078c9c4971ffc"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/gamestonk_terminal/jupyter/dashboards/dashboards_controller.py b/gamestonk_terminal/jupyter/dashboards/dashboards_controller.py
index e3a258f0f9f..f7f9f13000e 100644
--- a/gamestonk_terminal/jupyter/dashboards/dashboards_controller.py
+++ b/gamestonk_terminal/jupyter/dashboards/dashboards_controller.py
@@ -22,7 +22,7 @@ from gamestonk_terminal.menu import session
class DashboardsController(BaseController):
"""Dashboards Controller class"""
- CHOICES_COMMANDS = ["stocks", "correlation", "vsurf"]
+ CHOICES_COMMANDS = ["stocks", "correlation", "vsurf", "chains"]
PATH = "/jupyter/dashboard/"
def __init__(self, queue: List[str] = None):
@@ -36,9 +36,10 @@ class DashboardsController(BaseController):
def print_help(self):
"""Print help"""
help_text = """[cmds]
- stocks interactive dashboard with ticker information
- correlation interactive dashboard with correlation information
- vsurf options volatility surface[/cmds]
+ stocks historic stock information
+ correlation stock correlations
+ vsurf options volatility surface
+ chains options chain analysis[/cmds]
"""
console.print(text=help_text, menu="Jupyter - Dashboards")
@@ -54,6 +55,10 @@ class DashboardsController(BaseController):
"""Process vsurf command"""
create_call(other_args, "vsurf", "")
+ def call_chains(self, other_args: List[str]):
+ """Process vsurf command"""
+ create_call(other_args, "chains", "")
+
def create_call(other_args: List[str], name: str, filename: str = None) -> None:
filename = filename if filename else name
diff --git a/website/content/jupyter/dashboards/chains/_index.md b/website/content/jupyter/dashboards/chains/_index.md
new file mode 100644
index 00000000000..16dbcbf3ff6
--- /dev/null
+++ b/website/content/jupyter/dashboards/chains/_index.md
@@ -0,0 +1,11 @@
+```
+usage: chains [-j] [-h]
+```
+
+Opens a voila dashboard showing option chain information for a given ticker and expiration
+
+```
+optional arguments:
+ -j opens in jupyterlab instead of voila (default: False)
+ -h, --help show this help message
+```
diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml
index ad0016f3f70..f9a9c264976 100755
--- a/website/data/menu/main.yml
+++ b/website/data/menu/main.yml
@@ -1226,3 +1226,19 @@ main:
ref: "/common/prediction_techniques/conv1d"
- name: mc
ref: "/common/prediction_techniques/mc"
+ - name: jupyter
+ ref: "/jupyter"
+ sub:
+ - name: "dashboards"
+ ref: "/jupyter/dashboards"
+ sub:
+ - name: "stocks"
+ ref: "/jupyter/dashboards/stocks"
+ - name: "correlation"
+ ref: "/jupyter/dashboards/correlation"
+ - name: "vsurf"
+ ref: "/jupyter/dashboards/vsurf"
+ - name: "chains"
+ ref: "/jupyter/dashboards/chains"
+ - name: "reports"
+ ref: "/jupyter/reports"