diff --git a/exports/options/.gitkeep b/exports/options/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 4e06f5f025f..f0bcfef5977 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -424,13 +424,18 @@ Command|Description|Source
Command|Description|Source
------ | --------|----
+`disp` | Display all preset screeners filters
+`scr` | Output screener options
+`load` | Load new ticker
+`info` | Display option information | [Barchart](https://barchart.com/)
+`calc` | Basic option PnL calculator
`exp` | see/set expiry date
-`volume` | volume + open interest options trading plot |[Yahoo Finance](https://finance.yahoo.com/)
-`vcalls` | calls volume + open interest plot |[Yahoo Finance](https://finance.yahoo.com/)
-`vputs` | puts volume + open interest plot |[Yahoo Finance](https://finance.yahoo.com/)
`chains` | displays option chains |[Tradier](https://developer.tradier.com/)
-`info` | display option information | [Barchart](https://barchart.com/)
-
+`oi` | Plot open interest
+`vol` | Plot volume
+`voi` | Plot volume and open interest
+`hist` | Plot historical options data | [Tradier](https://developer.tradier.com/)
+`gr_hist` | Plot historical option greek | [Syncretism](ops.syncretism.io)
## Screener [»](screener/README.md)
@@ -470,7 +475,7 @@ lis | latest insider sales
blis | big latest insider sales ($100k+)
blos | big latest officer sales ($100k+)
blcs | big latest CEO/CFO sales ($100k+)
-Top |
+Top |
topt | top officer purchases today
toppw | top officer purchases past week
toppm | top officer purchases past month
diff --git a/gamestonk_terminal/cryptocurrency/binance/binance_view.py b/gamestonk_terminal/cryptocurrency/binance/binance_view.py
index 8ddbebee199..86e77576b2f 100644
--- a/gamestonk_terminal/cryptocurrency/binance/binance_view.py
+++ b/gamestonk_terminal/cryptocurrency/binance/binance_view.py
@@ -62,7 +62,7 @@ def plot_candles(candles_df: pd.DataFrame, title: str):
candles_df,
type="candle",
volume=True,
- title=f"{title}",
+ title=f"\n{title}",
xrotation=20,
style="binance",
figratio=(10, 7),
diff --git a/gamestonk_terminal/cryptocurrency/coinpaprika/coinpaprika_view.py b/gamestonk_terminal/cryptocurrency/coinpaprika/coinpaprika_view.py
index f653640006e..c0104f5e511 100644
--- a/gamestonk_terminal/cryptocurrency/coinpaprika/coinpaprika_view.py
+++ b/gamestonk_terminal/cryptocurrency/coinpaprika/coinpaprika_view.py
@@ -1319,7 +1319,7 @@ def chart(coin_id: str, other_args: List[str]):
]
df = df.set_index(pd.to_datetime(df["Time0"])).drop("Time0", axis=1)
title = (
- f"{coin_id}/{ns_parser.vs} from {df.index[0].strftime('%Y/%m/%d')} to {df.index[-1].strftime('%Y/%m/%d')}",
+ f"\n{coin_id}/{ns_parser.vs} from {df.index[0].strftime('%Y/%m/%d')} to {df.index[-1].strftime('%Y/%m/%d')}",
)
df["Volume"] = df["Volume"] / 1_000_000
mpf.plot(
diff --git a/gamestonk_terminal/options/README.md b/gamestonk_terminal/options/README.md
index 4717c256616..0e66e05003b 100644
--- a/gamestonk_terminal/options/README.md
+++ b/gamestonk_terminal/options/README.md
@@ -8,19 +8,27 @@ This menu aims to give insight on options. Options can cause significant share p
* Display all preset screeners filters
* [scr](#scr)
* Output screener options
-* [exp](#exp)
- * See/set expiry date [Yahoo Finance]
-* [voi](#voi)
- * Volume + open interest options trading plot [Yahoo Finance]
-* [vcalls](#vcalls)
- * Calls volume + open interest plot [Yahoo Finance]
-* [vputs](#vputs)
- * Puts volume + open interest plot [Yahoo Finance]
-* [chains](#chains)
- * Display option chains [Source: Tradier]
+* [load](#load)
+ * Load new ticker
* [info](#info)
- * _**This requires selenium webdriver installed**_
* Display option information [Source: Barchart.com]
+* [calc](#calc)
+ * basic call/put PnL calculator
+* [exp](#exp)
+ * See/set expiry date
+* [chains](#chains)
+ * Display option chains with greeks [Source: Tradier.com]
+* [oi](#oi)
+ * Plot open interest
+* [vol](#vol)
+ * Plot volume
+* [voi](#voi)
+ * Plot volume and open interest
+* [hist](#hist)
+ * Plot option history [Source: Tradier.com]
+* [gr_hist](#gr_hist)
+ * Plot historical option greek [Source: ops.syncretism.io]
+
## disp
@@ -35,7 +43,6 @@ View available presets under [presets folder](/gamestonk_terminal/options/preset
-
## scr
```text
@@ -50,67 +57,64 @@ Day Low; SMDH: Regular Market Day High; LU: Last Trade Date; LC: Last Crawl; ITM
+## load
+Load new ticker
+```python
+usage: load [-t Ticker]
+```
+* -t/--ticker: Ticker to load. Flag is optional, as `load aapl` will also work
+
+## info
+This scrapes the options information from barchart.com/stocks overview. This includes information such as Historical Volatility and IV Rank.
+
+````
+usage: info
+````
+
+
+
+## calc
+Basic profit calculator for options
+```python
+usage: calc [-s STRIKE] [-p PREMIUM] [--put] [--sell] [-h]
+```
+* -s/--strike : Strike price to consider
+* -p/--premium: Premium being paid
+* --put: Flag to calculate for put option
+* --sell: Flag to calculate for selling option
+
+
## exp
-
+See/set expiry dates.
```text
-usage: exp [-d {0,1,2,3,4,5,6,7,8,9,10,11}]
+usage: exp [-d] [-D
```
-See/set expiry dates. [Source: Yahoo Finance]
-
* -d : Expiry date index to set.
+* -D : Date to set
-
+Running `exp` will show available option expirations
+```python
+(✨) (op)> exp
-
-## voi
-
-```text
-usage: voi [-v MIN_VOL] [-m MIN_SP] [-M MAX_SP]
+Available expiry dates:
+ 0. 2021-07-30
+ 1. 2021-08-06
+ 2. 2021-08-13
+ 3. 2021-08-20
+ 4. 2021-08-27
+ 5. 2021-09-03
```
-
-Plots Volume + Open Interest of calls vs puts. [Source: Yahoo Finance]
-
-* -v : Minimum volume (considering open interest) threshold of the plot.
-* -m : Minimum strike price to consider in the plot.
-* -M : Maximum strike price to consider in the plot.
-
-
-
-
-## vcalls
-
-```text
-usage: vcalls [-m MIN_SP] [-M MAX_SP]
+To set 2021-08-13, the following two options are equivalent
+```python
+exp 2
+exp -D 2021-08-13
```
-
-Plots Calls Volume + Open Interest. [Source: Yahoo Finance]
-
-* -m : Minimum strike price to consider in the plot.
-* -M : Maximum strike price to consider in the plot.
-
-
-
-
-## vputs
-
-```text
-usage: vputs [-m MIN_SP] [-M MAX_SP]
-```
-
-Plots Puts Volume + Open Interest. [Source: Yahoo Finance]
-
-* -m : Minimum strike price to consider in the plot.
-* -M : Maximum strike price to consider in the plot.
-
-
-
-
## chains
````
-usage: chains [--calls] [--puts] [-m MIN_SP] [-M MAX_SP]
+usage: chains [--calls] [--puts] [-m MIN_SP] [-M MAX_SP] [-d] [--export]
````
Display options chains. [Source: Tradier]
@@ -119,23 +123,81 @@ Display options chains. [Source: Tradier]
* --puts : Flag to show puts only
* -m : Minimum strike price to consider.
* -M : Maximum strike price to consider.
-* -d : Columns to display. Should be comma separated. Must be in * -d : Columns to display from the following selection: bid, ask, strike, bidsize, asksize, volume, open_interest, delta, gamma, theta, vega, ask_iv, bid_iv, mid_iv. Default: mid_iv,vega_theta_gamma,delta,volume,open_interest,bid,ask. Note that they are separated by comma.
+* -d : Columns to display. Should be comma separated. Must be in * -d : Columns to display from the following selection: symbol, bid, ask, strike, bidsize, asksize, volume, open_interest, delta, gamma, theta, vega, iv. Note that they are separated by comma.
+* --export: Flag to export. Options are {csv,json,xlsx}.
+
+
+
+## oi
+Plot open interest.
+```python
+usage: oi [-m MIN] [-M MAX] [--calls] [--puts] [--source {tr,yf}] [-h]
+```
+* -m/--min: Min strike to plot. Defaults to .75 * current price
+* -M/--max: Max strike to plot. Defaults to 1.25 * current price
+* --calls: Flag to plot calls only
+* --puts: Flag to plot puts only
+* --source: Data source. Can be ['yf','tr']. Defaults to 'tr' for Tradier.
+
+
+
+## vol
+Plot volume.
+```python
+usage: vol [-m MIN] [-M MAX] [--calls] [--puts] [--source {tr,yf}] [-h]
+```
+* -m/--min: Min strike to plot. Defaults to .75 * current price
+* -M/--max: Max strike to plot. Defaults to 1.25 * current price
+* --calls: Flag to plot calls only
+* --puts: Flag to plot puts only
+* --source: Data source. Can be ['yf','tr']. Defaults to 'tr' for Tradier.
+
+
+## voi
+
+```text
+usage: voi [-v MIN_VOL] [-m MIN_SP] [-M MAX_SP] [--source]
+```
+
+Plots Volume + Open Interest of calls vs puts.
+
+* -v : Minimum volume (considering open interest) threshold of the plot.
+* -m : Minimum strike price to consider in the plot.
+* -M : Maximum strike price to consider in the plot.
+* --source: Data source. Can be ['yf','tr']. Defaults to 'tr' for Tradier.
+
+
+
+## hist
+Shows historical option chain
+```python
+usage: hist [-s STRIKE] [--put] [--chain CHAIN_ID] [--raw] [--export {csv,json,xlsx}] [-h]
+```
+* -s/--strike: Strike to show history for
+* --put: Flag to indicate the option is a put
+* --chain: OCC Chain ID. Example: AAPL210730C00144000. This flag overwrites the strike and put option
+* --raw: Show raw output
+* --export: Export file. Can be {csv, json, xlsx}.
+
+Note that the chain ID can be obtained by using `chains -d symbol`
+
+
-
+## gr_hist
+Plot historical option greeks. Data from ops.syncretism.io
+```python
+usage: gr_hist [-s STRIKE] [--put] [-g {iv,gamma,theta,vega,delta,rho,premium}] [--chain CHAIN_ID] [--raw] [--export {csv,json,xlsx}] [-h]
+```
+* -s/--strike: Strike to show history for
+* -g/--greek: Greek to get data for. Can be {iv,gamma,theta,vega,delta,rho}. Also can be premium
+* --put: Flag to indicate the option is a put
+* --chain: OCC Chain ID. Example: AAPL210730C00144000. This flag overwrites the strike and put option
+* --raw: Show raw output (last 20 entries)
+* --export: Export file. Can be {csv, json, xlsx}.
-## info
-This scrapes the options information from barchart.com/stocks overview. This includes information such as Historical Volatility and IV Rank.
-
-In order to run, the selenium webdriver must be installed.
-Currently, this runs on either Chrome or Firefox. The path to the driver should be defined in [config_terminal.py](#config_terminal.py).
-
-Note this may take more time than other commands to process.
-
-````
-usage: info [-d DRIVER]
-````
-* -d/--driver : One of {chrome, firefox}. This indicates which driver you have installed.
-
-
+```python
+(✨) (op)> gr_hist -s 145 --put -g delta
+```
+
diff --git a/gamestonk_terminal/options/__init__.py b/gamestonk_terminal/options/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gamestonk_terminal/options/barchart_view.py b/gamestonk_terminal/options/barchart_view.py
index e3f366e86e5..dc003098c6d 100644
--- a/gamestonk_terminal/options/barchart_view.py
+++ b/gamestonk_terminal/options/barchart_view.py
@@ -3,20 +3,13 @@ __docformat__ = "numpy"
import argparse
from typing import List
-from selenium import webdriver
-from bs4 import BeautifulSoup
-# from selenium.webdriver.chrome.options import Options as cOpts
-# from selenium.webdriver.firefox.options import Options as fOpts
+import requests
+from bs4 import BeautifulSoup
import pandas as pd
from tabulate import tabulate
-from gamestonk_terminal.config_terminal import (
- PATH_TO_SELENIUM_DRIVER as path_to_driver,
- WEBDRIVER_TO_USE as web,
-)
-from gamestonk_terminal.helper_funcs import parse_known_args_and_warn
-browsers = ["chrome", "firefox"]
+from gamestonk_terminal.helper_funcs import parse_known_args_and_warn
def print_options_data(stock: str, other_args: List[str]):
@@ -29,9 +22,6 @@ def print_options_data(stock: str, other_args: List[str]):
other_args: List[str]
Other arguments. Currently just accepts a browser flag for selenium
"""
- if path_to_driver is None:
- print("Please specify your selenium driver path in config_terminal.py", "\n")
- return
parser = argparse.ArgumentParser(
add_help=False,
@@ -39,38 +29,16 @@ def print_options_data(stock: str, other_args: List[str]):
prog="info",
description="Display option data [Source: Barchart.com]",
)
- parser.add_argument(
- "-b",
- "--browser",
- dest="browser",
- help="selenium webdriver to use",
- choices=browsers,
- default=web,
- )
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
- if ns_parser.browser:
- browser = ns_parser.browser
-
- if browser == "chrome":
- # commenting this because it breaks when in usage
- # the downside is that the browser will pop up to get the data
- # options = cOpts()
- # options.headless = True
- driver = webdriver.Chrome(executable_path=path_to_driver)
-
- elif browser == "firefox":
- # commenting this because it breaks when in usage
- # the downside is that the browser will pop up to get the data
- # options = fOpts()
- # options.headless = True
- driver = webdriver.Firefox(executable_path=path_to_driver)
+ if not ns_parser:
+ return
page = f"https://www.barchart.com/stocks/quotes/{stock}/overview"
- driver.get(page)
- soup = BeautifulSoup(driver.page_source, "html.parser")
+ r = requests.get(page, headers={"User-Agent": "Mozilla/5.0"})
+ soup = BeautifulSoup(r.text, "html.parser")
tags = soup.find(
"div",
attrs={
@@ -80,7 +48,6 @@ def print_options_data(stock: str, other_args: List[str]):
data = tags.find_all("li")
labels = []
values = []
- print("")
for row in data:
labels.append(row.find_all("span")[0].getText())
values.append(row.find_all("span")[1].getText())
diff --git a/gamestonk_terminal/options/calculator_model.py b/gamestonk_terminal/options/calculator_model.py
new file mode 100644
index 00000000000..6015cfe38ff
--- /dev/null
+++ b/gamestonk_terminal/options/calculator_model.py
@@ -0,0 +1,143 @@
+"""Calculator Model"""
+___docformat__ = "numpy"
+
+import argparse
+from typing import List
+
+import numpy as np
+from matplotlib import pyplot as plt
+
+from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale
+from gamestonk_terminal import config_plot as cfp
+from gamestonk_terminal import feature_flags as gtff
+
+
+def pnl_calculator(other_args: List[str]):
+ """Plot profit/loss for different option variables
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="calc",
+ description="Calculate profit or loss for given option settings.",
+ )
+
+ parser.add_argument(
+ "--put",
+ action="store_true",
+ default=False,
+ dest="put",
+ help="Flag to calculate put option",
+ )
+
+ parser.add_argument(
+ "--sell",
+ action="store_true",
+ default=False,
+ dest="sell",
+ help="Flag to get profit chart of selling contract",
+ )
+
+ parser.add_argument(
+ "-s",
+ "--strike",
+ type=float,
+ dest="strike",
+ help="Option strike price",
+ default=10,
+ )
+
+ parser.add_argument(
+ "-p", "--premium", type=float, dest="premium", help="Premium price", default=1
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+
+ if not ns_parser:
+ return
+
+ strike = ns_parser.strike
+ premium = ns_parser.premium
+
+ price_at_expiry = np.linspace(ns_parser.strike / 2, 1.5 * ns_parser.strike, 301)
+
+ sell = [1, -1][ns_parser.sell]
+ if ns_parser.put:
+ break_even = strike - sell * premium
+ pnl = strike - premium - price_at_expiry
+ pnl = sell * 100 * np.where(price_at_expiry < strike, pnl, -premium)
+
+ else:
+ break_even = strike + sell * premium
+ pnl = price_at_expiry - strike - premium
+ pnl = sell * 100 * np.where(price_at_expiry > strike, pnl, -premium)
+
+ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
+ ax.plot(price_at_expiry, pnl, alpha=0.1, c="k")
+ ax.fill_between(
+ price_at_expiry, 0, pnl, where=(pnl > 0), facecolor="green", alpha=0.5
+ )
+ ax.fill_between(
+ price_at_expiry, 0, pnl, where=(pnl < 0), facecolor="red", alpha=0.5
+ )
+ ax.axvline(
+ x=break_even, c="black", lw=3, alpha=0.6, label=f"Breakeven: ${break_even}"
+ )
+ ax.axvline(
+ x=strike, c="dodgerblue", lw=3, alpha=0.6, label=f"Strike: ${strike}"
+ )
+ if ns_parser.sell:
+ ax.axhline(
+ y=100 * premium,
+ c="seagreen",
+ lw=3,
+ alpha=0.6,
+ label=f"Max Profit: ${100*premium}",
+ )
+ else:
+ ax.axhline(
+ y=-100 * premium,
+ c="firebrick",
+ lw=3,
+ alpha=0.6,
+ label=f"Max Loss: ${-100 * premium}",
+ )
+
+ ax.set_xlabel("Price at Expiry")
+
+ ax.set_ylabel("Profit")
+ ax.set_title(
+ f"Profit for {['Buying','Selling'][ns_parser.sell]} {['Call', 'Put'][ns_parser.put]} option"
+ )
+ ax.grid(True)
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ plt.legend(loc=0)
+ fig.tight_layout(pad=1)
+ plt.show()
+ print(f"Strike: ${strike}")
+ print(f"Premium: ${premium}")
+ print(f"Breakeven price: ${break_even}")
+ if ns_parser.sell:
+ print(f"Max profit: ${100 * premium}")
+ print("Max loss: Unlimited")
+ else:
+ print("Max profit: Unlimited")
+ print(f"Max loss: ${-100*premium}")
+ print("")
+
+ except Exception as e:
+ print(e, "\n")
+ return
+
+ except SystemExit:
+ print("")
+ return
diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py
index 90e5a46473d..f2fd9589560 100644
--- a/gamestonk_terminal/options/op_controller.py
+++ b/gamestonk_terminal/options/op_controller.py
@@ -5,18 +5,22 @@ import argparse
import os
from typing import List
import matplotlib.pyplot as plt
-import pandas as pd
-import yfinance as yf
from prompt_toolkit.completion import NestedCompleter
-from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
+from gamestonk_terminal.helper_funcs import get_flair
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.options import (
- yahoo_view,
- tradier_view,
barchart_view,
syncretism_view,
+ calculator_model,
+ op_helpers,
+ yfinance_view,
+ yfinance_model,
+ tradier_view,
+ tradier_model,
)
+
+from gamestonk_terminal.config_terminal import TRADIER_TOKEN
from gamestonk_terminal.menu import session
@@ -32,86 +36,39 @@ class OptionsController:
"quit",
"disp",
"scr",
- ]
-
- CHOICES_TICKER_DEPENDENT = [
- "exp",
- "voi",
- "vcalls",
- "vputs",
- "chains",
+ "calc",
+ "yf",
+ "tr",
"info",
+ "load",
+ "exp",
+ "vol",
+ "voi",
+ "oi",
+ "hist",
+ "chains",
+ "gr_hist",
]
- def __init__(self, ticker: str, stock: pd.DataFrame):
+ def __init__(self, ticker: str):
"""Construct data."""
- if ticker:
- self.ticker = ticker
- 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 = stock["Adj Close"].values[-1]
-
- self.op_parser = argparse.ArgumentParser(add_help=False, prog="op")
- self.op_parser.add_argument(
- "cmd",
- choices=self.CHOICES + self.CHOICES_TICKER_DEPENDENT,
- )
- else:
- self.expiry_date = ""
- self.op_parser = argparse.ArgumentParser(add_help=False, prog="op")
- self.op_parser.add_argument(
- "cmd",
- choices=self.CHOICES,
- )
-
- def expiry_dates(self, other_args: List[str]):
- """Print all available expiry dates."""
- parser = argparse.ArgumentParser(
- add_help=False,
- prog="exp",
- description="""See/set expiry dates. [Source: Yahoo Finance]""",
- )
- parser.add_argument(
- "-d",
- "--date",
- dest="n_date",
- action="store",
- type=int,
- default=-1,
- choices=range(len(self.yf_ticker_data.options)),
- help=f"Expiry date index for {self.ticker}.",
- )
-
- try:
- if other_args:
- if "-" not in other_args[0]:
- other_args.insert(0, "-d")
-
- ns_parser = parse_known_args_and_warn(parser, other_args)
- if not ns_parser:
- return
-
- # Print possible expiry dates
- if ns_parser.n_date == -1:
- print("\nAvailable expiry dates:")
- 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
+ self.ticker = ticker
+ if self.ticker:
+ if TRADIER_TOKEN == "REPLACE_ME":
+ self.expiry_dates = yfinance_model.option_expirations(self.ticker)
else:
- 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}")
+ self.expiry_dates = tradier_model.option_expirations(self.ticker)
+ else:
+ self.expiry_dates = []
- except Exception as e:
- print(e)
+ self.selected_date = ""
+ self.op_parser = argparse.ArgumentParser(add_help=False, prog="op")
+ self.op_parser.add_argument(
+ "cmd",
+ choices=self.CHOICES,
+ )
- print("")
- return
-
- @staticmethod
- def print_help(expiry_date):
+ def print_help(self):
"""Print help."""
print(
"https://github.com/GamestonkTerminal/GamestonkTerminal/tree/main/gamestonk_terminal/options"
@@ -125,18 +82,22 @@ class OptionsController:
print(" disp display all preset screeners filters")
print(" scr output screener options")
print("")
- if expiry_date:
- print(f"Selected expiry date: {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("")
- print(" chains display option chains")
- print(
- " info display option information (volatility, IV rank etc)"
- )
+ print(f"Current Ticker: {self.ticker or None}")
+ print(" load load new ticker")
+ print(" info display option information (volatility, IV rank etc)")
+ print("")
+ print(" calc basic call/put PnL calculator")
+ print("")
+ print(f"Current Expiration: {self.selected_date or None}")
+ print(" exp see and set expiration dates")
+ print("")
+ if self.selected_date and self.ticker:
+ print(" chains display option chains with greeks [Tradier]]")
+ print(" oi plot open interest [Tradier/YF]")
+ print(" vol plot volume [Tradier/YF]")
+ print(" voi plot volume and open interest [Tradier/YF]")
+ print(" hist plot option history [Tradier")
+ print(" gr_hist plot option greek history [Syncretism]")
print("")
def switch(self, an_input: str):
@@ -159,7 +120,7 @@ class OptionsController:
# Help menu again
if known_args.cmd == "?":
- self.print_help(self.expiry_date)
+ self.print_help()
return None
# Clear screen
@@ -173,7 +134,7 @@ class OptionsController:
def call_help(self, _):
"""Process Help command."""
- self.print_help(self.expiry_date)
+ self.print_help()
def call_q(self, _):
"""Process Q command - quit the menu."""
@@ -183,59 +144,114 @@ class OptionsController:
"""Process Quit command - quit the program."""
return True
- def call_exp(self, other_args: List[str]):
- """Process exp command."""
- self.expiry_dates(other_args)
+ def call_calc(self, other_args: List[str]):
+ """Process calc command"""
+ calculator_model.pnl_calculator(other_args)
- def call_voi(self, other_args: List[str]):
- """Process voi command."""
- yahoo_view.plot_volume_open_interest(
- other_args,
- self.ticker,
- self.expiry_date,
- self.last_adj_close_price,
- self.options.calls,
- self.options.puts,
- )
-
- def call_vcalls(self, other_args: List[str]):
- """Process vcalls command."""
- yahoo_view.plot_calls_volume_open_interest(
- other_args,
- self.ticker,
- self.expiry_date,
- self.last_adj_close_price,
- self.options.calls,
- )
-
- def call_vputs(self, other_args: List[str]):
- """Process vcalls command."""
- yahoo_view.plot_puts_volume_open_interest(
- other_args,
- self.ticker,
- self.expiry_date,
- self.last_adj_close_price,
- self.options.puts,
- )
-
- def call_chains(self, other_args):
- tradier_view.display_chains(self.ticker, self.expiry_date, other_args)
-
- def call_info(self, other_args):
+ def call_info(self, other_args: List[str]):
+ """Process info command"""
barchart_view.print_options_data(self.ticker, other_args)
- def call_disp(self, other_args):
+ def call_disp(self, other_args: List[str]):
+ """Process disp command"""
syncretism_view.view_available_presets(other_args)
- def call_scr(self, other_args):
+ def call_scr(self, other_args: List[str]):
+ """Process scr command"""
syncretism_view.screener_output(other_args)
+ def call_load(self, other_args: List[str]):
+ """Process load command"""
+ self.ticker = op_helpers.load(other_args)
+ if TRADIER_TOKEN == "REPLACE_ME":
+ self.expiry_dates = yfinance_model.option_expirations(self.ticker)
+ else:
+ self.expiry_dates = tradier_model.option_expirations(self.ticker)
-def menu(ticker: str, stock: pd.DataFrame):
+ def call_exp(self, other_args: List[str]):
+ """Process exp command"""
+ if self.ticker:
+ self.selected_date = op_helpers.select_option_date(
+ self.expiry_dates, other_args
+ )
+ else:
+ print("Please select a ticker using load {ticker}", "\n")
+
+ def call_hist(self, other_args: List[str]):
+ if TRADIER_TOKEN != "REPLACE_ME":
+ tradier_view.display_historical(self.ticker, self.selected_date, other_args)
+ else:
+ print("TRADIER TOKEN not supplied. \n")
+
+ def call_chains(self, other_args: List[str]):
+ """Process chains command"""
+ if TRADIER_TOKEN != "REPLACE_ME":
+ tradier_view.display_chains(self.ticker, self.selected_date, other_args)
+ else:
+ print("TRADIER TOKEN not supplied. \n")
+
+ def call_vol(self, other_args: List[str]):
+ """Process vol command"""
+ if not self.ticker and not self.selected_date:
+ print("Ticker and expiration required.\n")
+ return
+ parsed = op_helpers.vol(other_args)
+ if not parsed:
+ return
+ if parsed.source == "tr" and TRADIER_TOKEN != "REPLACE_ME":
+ options = tradier_model.get_option_chains(self.ticker, self.selected_date)
+ tradier_view.plot_vol(options, self.ticker, self.selected_date, parsed)
+ else:
+ options = yfinance_model.get_option_chain(self.ticker, self.selected_date)
+ yfinance_view.plot_vol(
+ options.calls, options.puts, self.ticker, self.selected_date, parsed
+ )
+
+ def call_voi(self, other_args: List[str]):
+ """Process voi command"""
+ if not self.ticker and not self.selected_date:
+ print("Ticker and expiration required.")
+ return
+ parsed = op_helpers.voi(other_args)
+ if not parsed:
+ return
+ if parsed.source == "tr" and TRADIER_TOKEN != "REPLACE_ME":
+ options = tradier_model.get_option_chains(self.ticker, self.selected_date)
+ tradier_view.plot_volume_open_interest(
+ self.ticker, self.selected_date, options, parsed
+ )
+ else:
+ options = yfinance_model.get_option_chain(self.ticker, self.selected_date)
+ yfinance_view.plot_volume_open_interest(
+ self.ticker, self.selected_date, options.calls, options.puts, parsed
+ )
+
+ def call_oi(self, other_args: List[str]):
+ """Process oi command"""
+ if not self.ticker and not self.selected_date:
+ print("Ticker and expiration required.")
+ return
+ parsed = op_helpers.oi(other_args)
+ if not parsed:
+ return
+ if parsed.source == "tr" and TRADIER_TOKEN != "REPLACE_ME":
+ options = tradier_model.get_option_chains(self.ticker, self.selected_date)
+ tradier_view.plot_oi(options, self.ticker, self.selected_date, parsed)
+ else:
+ options = yfinance_model.get_option_chain(self.ticker, self.selected_date)
+ yfinance_view.plot_oi(
+ options.calls, options.puts, self.ticker, self.selected_date, parsed
+ )
+
+ def call_gr_hist(self, other_args: List[str]):
+ syncretism_view.historical_greeks(self.ticker, self.selected_date, other_args)
+
+
+def menu(ticker: str):
"""Options Menu."""
try:
- op_controller = OptionsController(ticker, stock)
+ op_controller = OptionsController(ticker)
op_controller.call_help(None)
except IndexError:
print("No options found for " + ticker)
diff --git a/gamestonk_terminal/options/op_helpers.py b/gamestonk_terminal/options/op_helpers.py
new file mode 100644
index 00000000000..f701fae7234
--- /dev/null
+++ b/gamestonk_terminal/options/op_helpers.py
@@ -0,0 +1,412 @@
+"""Option helper functions"""
+__docformat__ = "numpy"
+
+import argparse
+from typing import List
+
+import pandas as pd
+import numpy as np
+
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ check_non_negative,
+)
+
+# pylint: disable=R1710
+
+
+def load(other_args: List[str]) -> str:
+ """Load ticker into object
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Agrparse arguments
+
+ Returns
+ -------
+ str:
+ Ticker
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="opload",
+ description="Load a ticker into option menu",
+ )
+
+ parser.add_argument(
+ "-t",
+ "--ticker",
+ action="store",
+ dest="ticker",
+ required="-h" not in other_args,
+ help="Stock ticker",
+ )
+
+ try:
+ if other_args:
+ if "-t" not in other_args and "-h" not in other_args:
+ other_args.insert(0, "-t")
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return ""
+ print("")
+ return ns_parser.ticker
+ except Exception as e:
+ print(e, "\n")
+ return ""
+ except SystemExit:
+ print("")
+ return ""
+
+
+# pylint: disable=no-else-return
+
+
+def select_option_date(avalaiable_dates: List[str], other_args: List[str]) -> str:
+ """Select an option date out of a supplied list
+
+ Parameters
+ ----------
+ avalaiable_dates: List[str]
+ Possible date options
+ other_args: List[str]
+ Arparse arguments
+ Returns
+ -------
+ expiry_date: str
+ Selected expiry date
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="exp",
+ description="See and set expiration date",
+ )
+ parser.add_argument(
+ "-d",
+ "--date",
+ dest="n_date",
+ action="store",
+ type=int,
+ default=-1,
+ choices=range(len(avalaiable_dates)),
+ help="Select index for expiry date.",
+ )
+
+ parser.add_argument(
+ "-D",
+ dest="date",
+ type=str,
+ choices=avalaiable_dates + [""],
+ help="Select date (YYYY-MM-DD)",
+ default="",
+ )
+
+ try:
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-d")
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return ""
+
+ # Print possible expiry dates
+ if ns_parser.n_date == -1 and not ns_parser.date:
+ print("\nAvailable expiry dates:")
+ for i, d in enumerate(avalaiable_dates):
+ print(f" {(2 - len(str(i))) * ' '}{i}. {d}")
+ print("")
+ return ""
+
+ # It means an expiry date was correctly selected
+ else:
+ if ns_parser.date:
+ if ns_parser.date in avalaiable_dates:
+ print(f"Expiraration set to {ns_parser.date} \n")
+ return ns_parser.date
+ else:
+ print("Expiration not an option")
+ return ""
+ else:
+ expiry_date = avalaiable_dates[ns_parser.n_date]
+ print(f"Expiraration set to {expiry_date} \n")
+ return expiry_date
+
+ except Exception as e:
+ print(e, "\n")
+ return ""
+
+
+def get_loss_at_strike(strike: float, chain: pd.DataFrame) -> float:
+ """Function to get the loss at the given expiry
+
+ Parameters
+ ----------
+ strike: Union[int,float]
+ Value to calculate total loss at
+ chain: Dataframe:
+ Dataframe containing at least strike and openInterest
+
+ Returns
+ -------
+ loss: Union[float,int]
+ Total loss
+ """
+
+ itm_calls = chain[chain.index < strike][["OI_call"]]
+ itm_calls["loss"] = (strike - itm_calls.index) * itm_calls["OI_call"]
+ call_loss = itm_calls["loss"].sum()
+
+ itm_puts = chain[chain.index > strike][["OI_put"]]
+ itm_puts["loss"] = (itm_puts.index - strike) * itm_puts["OI_put"]
+ put_loss = itm_puts.loss.sum()
+ loss = call_loss + put_loss
+
+ return loss
+
+
+def calculate_max_pain(chain: pd.DataFrame) -> int:
+ """Returns the max pain for a given call/put dataframe
+
+ Parameters
+ ----------
+ chain: DataFrame
+ Dataframe to calculate value from
+
+ Returns
+ -------
+ max_pain : int
+ Max pain value
+ """
+
+ strikes = np.array(chain.index)
+ if ("OI_call" not in chain.columns) or ("OI_put" not in chain.columns):
+ print("Incorrect columns. Unable to parse max pain")
+ return np.nan
+
+ loss = []
+ for price_at_exp in strikes:
+ loss.append(get_loss_at_strike(price_at_exp, chain))
+
+ chain["loss"] = loss
+ max_pain = chain["loss"].idxmin()
+
+ return max_pain
+
+
+def vol(other_args: List[str]):
+ """Parse volume argparse
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+
+ Returns
+ -------
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="vol",
+ description="Plot volume. Volume refers to the number of contracts traded today.",
+ )
+
+ parser.add_argument(
+ "-m",
+ "--min",
+ default=-1,
+ type=check_non_negative,
+ help="Min strike to plot",
+ dest="min",
+ )
+ parser.add_argument(
+ "-M",
+ "--max",
+ default=-1,
+ type=check_non_negative,
+ help="Max strike to plot",
+ dest="max",
+ )
+
+ parser.add_argument(
+ "--calls",
+ action="store_true",
+ default=False,
+ dest="calls",
+ help="Flag to plot call options only",
+ )
+
+ parser.add_argument(
+ "--puts",
+ action="store_true",
+ default=False,
+ dest="puts",
+ help="Flag to plot put options only",
+ )
+
+ parser.add_argument(
+ "--source",
+ type=str,
+ default="tr",
+ choices=["tr", "yf"],
+ dest="source",
+ help="Source to get data from",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ return ns_parser
+
+ except Exception as e:
+ print(e, "\n")
+
+
+def voi(other_args: List[str]):
+ """Parse Volume + open interest argparse
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+
+ Returns
+ -------
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="voi",
+ description="""
+ Plots Volume + Open Interest of calls vs puts.
+ """,
+ )
+ 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.",
+ )
+ parser.add_argument(
+ "--source",
+ type=str,
+ default="tr",
+ choices=["tr", "yf"],
+ dest="source",
+ help="Source to get data from",
+ )
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return None
+ return ns_parser
+
+ except Exception as e:
+ print(e, "\n")
+ return None
+
+
+def oi(other_args: List[str]):
+ """Parse Open Interest argparse
+
+ Parameters
+ ----------
+ other_args: List[str]
+ Argparse arguments
+
+ Returns
+ -------
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="oi",
+ description="Plot open interest. Open interest represents the number of contracts that exist.",
+ )
+
+ parser.add_argument(
+ "-m",
+ "--min",
+ default=-1,
+ type=check_non_negative,
+ help="Min strike to plot",
+ dest="min",
+ )
+ parser.add_argument(
+ "-M",
+ "--max",
+ default=-1,
+ type=check_non_negative,
+ help="Max strike to plot",
+ dest="max",
+ )
+
+ parser.add_argument(
+ "--calls",
+ action="store_true",
+ default=False,
+ dest="calls",
+ help="Flag to plot call options only",
+ )
+
+ parser.add_argument(
+ "--puts",
+ action="store_true",
+ default=False,
+ dest="puts",
+ help="Flag to plot put options only",
+ )
+ parser.add_argument(
+ "--source",
+ type=str,
+ default="tr",
+ choices=["tr", "yf"],
+ dest="source",
+ help="Source to get data from",
+ )
+
+ try:
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+
+ if not ns_parser:
+ return None
+
+ return ns_parser
+
+ except Exception as e:
+ print(e, "\n")
+ return None
diff --git a/gamestonk_terminal/options/syncretism_view.py b/gamestonk_terminal/options/syncretism_view.py
index ccc9df5e09b..9a515e63653 100644
--- a/gamestonk_terminal/options/syncretism_view.py
+++ b/gamestonk_terminal/options/syncretism_view.py
@@ -5,10 +5,20 @@ import argparse
import os
from typing import List
import configparser
+
import requests
import pandas as pd
+import matplotlib.pyplot as plt
from tabulate import tabulate
-from gamestonk_terminal.helper_funcs import parse_known_args_and_warn
+
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ export_data,
+ plot_autoscale,
+)
+from gamestonk_terminal.options import yfinance_model
+from gamestonk_terminal import config_plot as cfp
+from gamestonk_terminal import feature_flags as gtff
presets_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "presets/")
@@ -191,3 +201,197 @@ PC: Price Change; PB: Price-to-book. """,
except Exception as e:
print(e, "\n")
+
+
+possible_greeks = [
+ "iv",
+ "gamma",
+ "theta",
+ "vega",
+ "delta",
+ "rho",
+ "premium",
+]
+
+
+def check_valid_option_greek_header(headers: str) -> List[str]:
+ """Check valid greek selection
+
+ Parameters
+ ----------
+ headers : str
+ Option chains headers
+
+ Returns
+ ----------
+ List[str]
+ List of columns string
+ """
+ columns = [str(item) for item in headers.split(",")]
+
+ for header in columns:
+ if header not in possible_greeks:
+ raise argparse.ArgumentTypeError("Invalid option chains header selected!")
+
+ return columns
+
+
+def historical_greeks(ticker: str, expiry: str, other_args: List[str]):
+ """Get historical greeks
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker
+ expiry: str
+ Expiration date
+ other_args: List[str]
+ Argparse arguments
+ """
+
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="gr_hist",
+ description="Plot historical option greeks.",
+ )
+
+ parser.add_argument(
+ "-s",
+ "--strike",
+ dest="strike",
+ type=float,
+ required="--chain" not in other_args or "-h" not in other_args,
+ help="Strike price to look at",
+ )
+ parser.add_argument(
+ "--put",
+ dest="put",
+ action="store_true",
+ default=False,
+ help="Flag for showing put option",
+ )
+
+ parser.add_argument(
+ "-g",
+ "--greek",
+ dest="greek",
+ type=str,
+ choices=possible_greeks,
+ default="delta",
+ help="Greek column to select",
+ )
+
+ parser.add_argument("--chain", dest="chain_id", type=str, help="OCC option symbol")
+
+ parser.add_argument(
+ "--raw", dest="raw", action="store_true", default=False, help="Display raw data"
+ )
+
+ parser.add_argument(
+ "--export",
+ choices=["csv", "json", "xlsx"],
+ default="",
+ dest="export",
+ help="Export dataframe data to csv,json,xlsx file",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ if not ns_parser.chain_id:
+ options = yfinance_model.get_option_chain(ticker, expiry)
+
+ if ns_parser.put:
+ options = options.puts
+ else:
+ options = options.calls
+
+ chain_id = options.loc[
+ options.strike == ns_parser.strike, "contractSymbol"
+ ].values[0]
+ else:
+ chain_id = ns_parser.chain_id
+
+ r = requests.get(f"https://api.syncretism.io/ops/historical/{chain_id}")
+
+ if r.status_code != 200:
+ print("Error in request.")
+ return
+
+ history = r.json()
+
+ iv, delta, gamma, theta, rho, vega, premium, price, time = (
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ )
+
+ for entry in history:
+
+ time.append(pd.to_datetime(entry["timestamp"], unit="s"))
+ iv.append(entry["impliedVolatility"])
+ gamma.append(entry["gamma"])
+ delta.append(entry["delta"])
+ theta.append(entry["theta"])
+ rho.append(entry["rho"])
+ vega.append(entry["vega"])
+ premium.append(entry["premium"])
+ price.append(entry["regularMarketPrice"])
+
+ data = {
+ "iv": iv,
+ "gamma": gamma,
+ "delta": delta,
+ "theta": theta,
+ "rho": rho,
+ "vega": vega,
+ "premium": premium,
+ "price": price,
+ }
+
+ df = pd.DataFrame(data, index=time)
+
+ if ns_parser.raw:
+ print(df.tail(20))
+ if ns_parser.export:
+ export_data(
+ ns_parser.export,
+ os.path.dirname(os.path.abspath(__file__)),
+ f"historical_greek_{ticker}_{expiry}_{ns_parser.greek}_{str(ns_parser.strike).replace('.', 'p')}"
+ f"_{['Call','Put'][ns_parser.put]}",
+ df,
+ )
+ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
+ im1 = ax.plot(time, df[ns_parser.greek], c="firebrick", label=ns_parser.greek)
+ ax.set_ylabel(ns_parser.greek)
+ ax1 = ax.twinx()
+ im2 = ax1.plot(time, price, c="dodgerblue", label="Stock Price")
+ ax1.set_ylabel(f"{ticker} Price")
+ ax1.set_xlabel("Date")
+ ax.grid("on")
+ ax.set_title(
+ f"{ns_parser.greek} historical for {ticker.upper()} {ns_parser.strike} {['Call','Put'][ns_parser.put]}"
+ )
+ plt.gcf().autofmt_xdate()
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ ims = im1 + im2
+ labels = [lab.get_label() for lab in ims]
+ plt.legend(ims, labels, loc=0)
+ fig.tight_layout(pad=1)
+ plt.show()
+ print("")
+
+ except Exception as e:
+ print(e, "\n")
diff --git a/gamestonk_terminal/options/tradier_model.py b/gamestonk_terminal/options/tradier_model.py
new file mode 100644
index 00000000000..036b3bbef38
--- /dev/null
+++ b/gamestonk_terminal/options/tradier_model.py
@@ -0,0 +1,188 @@
+"""Tradier options model"""
+__docformat__ = "numpy"
+
+import requests
+import pandas as pd
+
+from gamestonk_terminal import config_terminal as cfg
+
+option_columns = [
+ "symbol",
+ "bid",
+ "ask",
+ "strike",
+ "bidsize",
+ "asksize",
+ "volume",
+ "open_interest",
+ "option_type",
+]
+greek_columns = ["delta", "gamma", "theta", "vega", "ask_iv", "bid_iv", "mid_iv"]
+df_columns = option_columns + greek_columns
+
+default_columns = [
+ "mid_iv",
+ "vega",
+ "delta",
+ "gamma",
+ "theta",
+ "volume",
+ "open_interest",
+ "bid",
+ "ask",
+]
+
+# pylint: disable=no-else-return
+
+
+def option_expirations(ticker: str):
+ """Get available expiration dates for given ticker
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker to get expirations for
+
+ Returns
+ -------
+ dates: List[str]
+ List of of available expirations
+ """
+ r = requests.get(
+ "https://sandbox.tradier.com/v1/markets/options/expirations",
+ params={"symbol": ticker, "includeAllRoots": "true", "strikes": "false"},
+ headers={
+ "Authorization": f"Bearer {cfg.TRADIER_TOKEN}",
+ "Accept": "application/json",
+ },
+ )
+ if r.status_code == 200:
+ dates = r.json()["expirations"]["date"]
+ return dates
+ else:
+ print("Tradier request failed. Check token. \n")
+ return []
+
+
+def get_option_chains(symbol: str, expiry: str) -> pd.DataFrame:
+ """Display option chains [Source: Tradier]"
+
+ Parameters
+ ----------
+ symbol : str
+ Ticker to get options for
+ expiry : str
+ Expiration date in the form of "YYYY-MM-DD"
+
+ Returns
+ -------
+ chains: pd.DataFrame
+ Dataframe with options for the given Symbol and Expiration date
+ """
+ params = {"symbol": symbol, "expiration": expiry, "greeks": "true"}
+
+ headers = {
+ "Authorization": f"Bearer {cfg.TRADIER_TOKEN}",
+ "Accept": "application/json",
+ }
+
+ response = requests.get(
+ "https://sandbox.tradier.com/v1/markets/options/chains",
+ params=params,
+ headers=headers,
+ )
+ if response.status_code != 200:
+ print("Error in request. Check TRADIER_TOKEN\n")
+ return pd.DataFrame()
+
+ chains = process_chains(response)
+ return chains
+
+
+def process_chains(response: requests.models.Response) -> pd.DataFrame:
+ """Function to take in the requests.get and return a DataFrame
+
+ Parameters
+ ----------
+ response: requests.models.Response
+ This is the response from tradier api.
+
+ Returns
+ -------
+ opt_chain: pd.DataFrame
+ Dataframe with all available options
+ """
+ json_response = response.json()
+ options = json_response["options"]["option"]
+
+ opt_chain = pd.DataFrame(columns=df_columns)
+ for idx, option in enumerate(options):
+ data = [option[col] for col in option_columns]
+ data += [option["greeks"][col] for col in greek_columns]
+ opt_chain.loc[idx, :] = data
+
+ return opt_chain
+
+
+def last_price(ticker: str):
+ """Makes api request for last price
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker
+
+ Returns
+ -------
+ float:
+ Last price
+ """
+ r = requests.get(
+ "https://sandbox.tradier.com/v1/markets/quotes",
+ params={"symbols": ticker, "includeAllRoots": "true", "strikes": "false"},
+ headers={
+ "Authorization": f"Bearer {cfg.TRADIER_TOKEN}",
+ "Accept": "application/json",
+ },
+ )
+ if r.status_code == 200:
+ return float(r.json()["quotes"]["quote"]["last"])
+ else:
+ print("Error getting last price")
+ return None
+
+
+def historical_prices(symbol: str) -> pd.DataFrame:
+ """Get historical options prices
+
+ Parameters
+ ----------
+ symbol: str
+ OCC option chain symbol
+
+ Returns
+ -------
+ df_hist: pd.DataFrame
+ Dataframe of historical options
+ """
+ response = requests.get(
+ "https://sandbox.tradier.com/v1/markets/history",
+ params={"symbol": {symbol}, "interval": "daily"},
+ headers={
+ "Authorization": f"Bearer {cfg.TRADIER_TOKEN}",
+ "Accept": "application/json",
+ },
+ )
+
+ if response.status_code != 200:
+ print("Error with request")
+ return pd.DataFrame()
+
+ data = response.json()["history"]
+ if not data:
+ print("No historical data available")
+ return pd.DataFrame()
+
+ df_hist = pd.DataFrame(data["day"]).set_index("date")
+
+ return df_hist
diff --git a/gamestonk_terminal/options/tradier_view.py b/gamestonk_terminal/options/tradier_view.py
index 6aeee175cd4..4043b2bd43f 100644
--- a/gamestonk_terminal/options/tradier_view.py
+++ b/gamestonk_terminal/options/tradier_view.py
@@ -1,43 +1,63 @@
-"""Helper functions for the options menu"""
+"""Tradier options view"""
__docformat__ = "numpy"
import argparse
from typing import List
-import requests
-import pandas as pd
-import numpy as np
-from tabulate import tabulate
-from gamestonk_terminal.config_terminal import TRADIER_TOKEN
-from gamestonk_terminal.helper_funcs import (
- check_non_negative,
- parse_known_args_and_warn,
-)
+from bisect import bisect_left
+import os
+
+import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+import seaborn as sns
+import mplfinance as mpf
+from colorama import Fore, Style
+from tabulate import tabulate
+
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ check_non_negative,
+ export_data,
+ plot_autoscale,
+ patch_pandas_text_adjustment,
+)
+from gamestonk_terminal.options import op_helpers, tradier_model
+from gamestonk_terminal import config_plot as cfp
+from gamestonk_terminal import feature_flags as gtff
-option_columns = [
- "bid",
- "ask",
- "strike",
- "bidsize",
- "asksize",
- "volume",
- "open_interest",
- "option_type",
-]
-greek_columns = ["delta", "gamma", "theta", "vega", "ask_iv", "bid_iv", "mid_iv"]
-df_columns = option_columns + greek_columns
column_map = {"mid_iv": "iv", "open_interest": "oi", "volume": "vol"}
-default_columns = [
- "mid_iv",
- "vega",
- "delta",
- "gamma",
- "theta",
- "volume",
- "open_interest",
- "bid",
- "ask",
-]
+
+def red_highlight(val):
+ """Red highlight
+
+ Parameters
+ ----------
+ val
+ dataframe values to color
+
+ Returns
+ ----------
+ str
+ colored dataframes values
+ """
+ return f"{Fore.RED}{val}{Style.RESET_ALL}"
+
+
+def green_highlight(val):
+ """Green highlight
+
+ Parameters
+ ----------
+ values : List[str]
+ dataframe values to color
+
+ Returns
+ ----------
+ List[str]
+ colored dataframes values
+ """
+ return f"{Fore.GREEN}{val}{Style.RESET_ALL}"
def check_valid_option_chains_headers(headers: str) -> List[str]:
@@ -56,87 +76,29 @@ def check_valid_option_chains_headers(headers: str) -> List[str]:
columns = [str(item) for item in headers.split(",")]
for header in columns:
- if header not in df_columns:
+ if header not in tradier_model.df_columns:
raise argparse.ArgumentTypeError("Invalid option chains header selected!")
return columns
-def process_chains(response: requests.models.Response) -> pd.DataFrame:
- """Function to take in the requests.get and return a DataFrame
+def display_chains(ticker: str, expiry: str, other_args: List[str]):
+ """Display option chain
Parameters
----------
- response: requests.models.Response
- This is the response from tradier api.
-
- Returns
- -------
- opt_chain: pd.DataFrame
- Dataframe with all available options
- """
- json_response = response.json()
- options = json_response["options"]["option"]
-
- opt_chain = pd.DataFrame(columns=df_columns)
- for idx, option in enumerate(options):
- data = [option[col] for col in option_columns]
- data += [option["greeks"][col] for col in greek_columns]
- opt_chain.loc[idx, :] = data
-
- return opt_chain
-
-
-def get_option_chains(symbol: str, expiry: str) -> pd.DataFrame:
- """Display option chains [Source: Tradier]"
-
- Parameters
- ----------
- symbol : str
- Ticker to get options for
- expiry : str
- Expiration date in the form of "YYYY-MM-DD"
-
- Returns
- -------
- pd.DataFrame
- Dataframe with options for the gievn Symbol and Expiration date
- """
- params = {"symbol": symbol, "expiration": expiry, "greeks": "true"}
-
- headers = {"Authorization": f"Bearer {TRADIER_TOKEN}", "Accept": "application/json"}
-
- response = requests.get(
- "https://sandbox.tradier.com/v1/markets/options/chains",
- params=params,
- headers=headers,
- )
- if response.status_code != 200:
- print("Error in request. Check TRADIER_TOKEN\n")
- return pd.DataFrame()
-
- chains = process_chains(response)
- return chains
-
-
-def display_chains(symbol: str, expiry: str, other_args: List[str]):
- """Check valid option chains headers
-
- Parameters
- ----------
- headers : str
- Option chains headers
-
- Returns
- ----------
- List[str]
- List of columns string
+ ticker: str
+ Stock ticker
+ expiry: str
+ Expiration date of option
+ other_args: List[str]
+ Argparse arguments
"""
parser = argparse.ArgumentParser(
prog="chains",
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- description="Display option chains [Source: Tradier]",
+ description="Display option chains",
)
parser.add_argument(
"--calls",
@@ -172,22 +134,25 @@ def display_chains(symbol: str, expiry: str, other_args: List[str]):
"-d",
"--display",
dest="to_display",
- default=default_columns,
+ default=tradier_model.default_columns,
type=check_valid_option_chains_headers,
help="columns to look at. Columns can be: {bid, ask, strike, bidsize, asksize, volume, open_interest, delta, "
"gamma, theta, vega, ask_iv, bid_iv, mid_iv} ",
)
+ parser.add_argument(
+ "--export",
+ choices=["csv", "json", "xlsx"],
+ default="",
+ dest="export",
+ help="Export dataframe data to csv,json,xlsx file",
+ )
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
- chains_df = get_option_chains(symbol, expiry)
-
- if chains_df.empty:
- return
-
+ chains_df = tradier_model.get_option_chains(ticker, expiry)
columns = ns_parser.to_display + ["strike", "option_type"]
chains_df = chains_df[columns].rename(columns=column_map)
@@ -205,6 +170,16 @@ def display_chains(symbol: str, expiry: str, other_args: List[str]):
chains_df = chains_df[chains_df["strike"] >= min_strike]
chains_df = chains_df[chains_df["strike"] <= max_strike]
+
+ if ns_parser.export:
+ # Note the extra dirname needed due to the subfolder in options
+ export_data(
+ ns_parser.export,
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+ f"chains_{ticker}_{expiry}",
+ chains_df,
+ )
+
calls_df = chains_df[chains_df.option_type == "call"].drop(
columns=["option_type"]
)
@@ -238,6 +213,16 @@ def display_chains(symbol: str, expiry: str, other_args: List[str]):
puts_df = puts_df[puts_df.columns[::-1]]
chain_table = calls_df.merge(puts_df, on="strike")
+ if gtff.USE_COLOR:
+ call_cols = [col for col in chain_table if col.endswith("_x")]
+ put_cols = [col for col in chain_table if col.endswith("_y")]
+ patch_pandas_text_adjustment()
+ pd.set_option("display.max_colwidth", 0)
+ pd.set_option("display.max_rows", None)
+ for cc in call_cols:
+ chain_table[cc] = chain_table[cc].astype(str).apply(green_highlight)
+ for pc in put_cols:
+ chain_table[pc] = chain_table[pc].astype(str).apply(red_highlight)
headers = [
col.strip("_x")
if col.endswith("_x")
@@ -255,7 +240,466 @@ def display_chains(symbol: str, expiry: str, other_args: List[str]):
floatfmt=".2f",
)
)
- print("")
+ print("")
except Exception as e:
print(e, "\n")
+
+
+def plot_oi(
+ options: pd.DataFrame,
+ ticker: str,
+ expiry: str,
+ ns_parser: argparse.Namespace,
+):
+ """Plot open interest
+
+ Parameters
+ ----------
+ options: pd.DataFrame
+ Options dataframe with both calls and puts
+ ticker: str
+ Ticker
+ expiry: str
+ Expiry date for options
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ current_price = tradier_model.last_price(ticker)
+
+ if ns_parser.min == -1:
+ min_strike = 0.75 * current_price
+ else:
+ min_strike = ns_parser.min
+
+ if ns_parser.max == -1:
+ max_strike = 1.25 * current_price
+ else:
+ max_strike = ns_parser.max
+
+ if ns_parser.calls and ns_parser.puts:
+ print("Both flags selected, please select one", "\n")
+ return
+
+ calls = options[options.option_type == "call"][["strike", "open_interest"]]
+ puts = options[options.option_type == "put"][["strike", "open_interest"]]
+ call_oi = calls.set_index("strike")["open_interest"] / 1000
+ put_oi = puts.set_index("strike")["open_interest"] / 1000
+
+ df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True)
+ df_opt = df_opt.rename(
+ columns={"open_interest_x": "OI_call", "open_interest_y": "OI_put"}
+ )
+
+ max_pain = op_helpers.calculate_max_pain(df_opt)
+ plt.style.use("classic")
+ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
+
+ if not ns_parser.calls:
+ put_oi.plot(
+ x="strike",
+ y="open_interest",
+ label="Puts",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="r",
+ )
+ if not ns_parser.puts:
+ call_oi.plot(
+ x="strike",
+ y="open_interest",
+ label="Calls",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="g",
+ )
+ ax.axvline(
+ current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7
+ )
+ ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.7)
+ ax.grid("on")
+ ax.set_xlabel("Strike Price")
+ ax.set_ylabel("Open Interest (1k) ")
+ ax.set_xlim(min_strike, max_strike)
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}")
+ plt.legend(loc=0)
+ fig.tight_layout(pad=1)
+
+ plt.show()
+ plt.style.use("default")
+ print("")
+
+
+def plot_vol(
+ options: pd.DataFrame,
+ ticker: str,
+ expiry: str,
+ ns_parser: argparse.Namespace,
+):
+ """Plot volume
+
+ Parameters
+ ----------
+ options: pd.DataFrame
+ Options dataframe with both calls and puts
+ ticker: str
+ Ticker
+ expiry: str
+ Expiry date for options
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ current_price = tradier_model.last_price(ticker)
+
+ if ns_parser.min == -1:
+ min_strike = 0.75 * current_price
+ else:
+ min_strike = ns_parser.min
+
+ if ns_parser.max == -1:
+ max_strike = 1.25 * current_price
+ else:
+ max_strike = ns_parser.max
+
+ if ns_parser.calls and ns_parser.puts:
+ print("Both flags selected, please select one", "\n")
+ return
+
+ calls = options[options.option_type == "call"][["strike", "volume"]]
+ puts = options[options.option_type == "put"][["strike", "volume"]]
+ call_v = calls.set_index("strike")["volume"] / 1000
+ put_v = puts.set_index("strike")["volume"] / 1000
+ plt.style.use("classic")
+ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
+
+ if not ns_parser.calls:
+ put_v.plot(
+ x="strike",
+ y="volume",
+ label="Puts",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="r",
+ )
+ if not ns_parser.puts:
+ call_v.plot(
+ x="strike",
+ y="volume",
+ label="Calls",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="g",
+ )
+ ax.axvline(current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7)
+ ax.grid("on")
+ ax.set_xlabel("Strike Price")
+ ax.set_ylabel("Volume (1k) ")
+ ax.set_xlim(min_strike, max_strike)
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ ax.set_title(f"Volume for {ticker.upper()} expiring {expiry}")
+ plt.legend(loc=0)
+ fig.tight_layout(pad=1)
+
+ plt.show()
+ plt.style.use("default")
+ print("")
+
+
+def plot_volume_open_interest(
+ ticker: str, exp_date: str, options: pd.DataFrame, ns_parser: argparse.Namespace
+):
+ """Plot volume and open interest
+
+ Parameters
+ ----------
+ ticker : str
+ Main ticker to compare income
+ exp_date : str
+ Expiry date of the option
+ Options: pd.DataFrame
+ Options dataframe with both calls and puts
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ current_price = tradier_model.last_price(ticker)
+ calls = options[options.option_type == "call"][
+ ["strike", "volume", "open_interest"]
+ ]
+ puts = options[options.option_type == "put"][["strike", "volume", "open_interest"]]
+
+ # Process Calls Data
+ df_calls = calls.pivot_table(
+ index="strike", values=["volume", "open_interest"], aggfunc="sum"
+ ).reindex()
+ df_calls["strike"] = df_calls.index
+ df_calls["type"] = "calls"
+ df_calls["open_interest"] = df_calls["open_interest"]
+ df_calls["volume"] = df_calls["volume"]
+ df_calls["oi+v"] = df_calls["open_interest"] + df_calls["volume"]
+ df_calls["spot"] = round(current_price, 2)
+
+ df_puts = puts.pivot_table(
+ index="strike", values=["volume", "open_interest"], aggfunc="sum"
+ ).reindex()
+ df_puts["strike"] = df_puts.index
+ df_puts["type"] = "puts"
+ df_puts["open_interest"] = df_puts["open_interest"]
+ df_puts["volume"] = -df_puts["volume"]
+ df_puts["open_interest"] = -df_puts["open_interest"]
+ df_puts["oi+v"] = df_puts["open_interest"] + df_puts["volume"]
+ df_puts["spot"] = round(current_price, 2)
+
+ call_oi = calls.set_index("strike")["open_interest"] / 1000
+ put_oi = puts.set_index("strike")["open_interest"] / 1000
+
+ df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True)
+ df_opt = df_opt.rename(
+ columns={"open_interest_x": "OI_call", "open_interest_y": "OI_put"}
+ )
+
+ max_pain = op_helpers.calculate_max_pain(df_opt)
+
+ 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=cfp.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, current_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(3)
+
+ # 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()
+ plt.style.use("default")
+ print("")
+
+
+def display_historical(ticker: str, expiry: str, other_args: List[str]):
+ """Plot historical option data
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker
+ expiry: str
+ Expiration of option
+ other_args: List[str]
+ Argparse arguments
+ """
+
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ prog="hist",
+ description="Gets historical quotes for given option chain",
+ )
+ parser.add_argument(
+ "-s",
+ "--strike",
+ dest="strike",
+ type=float,
+ required="--chain" not in other_args or "-h" not in other_args,
+ help="Strike price to look at",
+ )
+ parser.add_argument(
+ "--put",
+ dest="put",
+ action="store_true",
+ default=False,
+ help="Flag for showing put option",
+ )
+ parser.add_argument("--chain", dest="chain_id", type=str, help="OCC option symbol")
+
+ parser.add_argument(
+ "--raw", dest="raw", action="store_true", default=False, help="Display raw data"
+ )
+
+ parser.add_argument(
+ "--export",
+ choices=["csv", "json", "xlsx"],
+ default="",
+ dest="export",
+ help="Export dataframe data to csv,json,xlsx file",
+ )
+
+ try:
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ if not ns_parser.chain_id:
+ strike = float(ns_parser.strike)
+ op_type = ["call", "put"][ns_parser.put]
+ chain = tradier_model.get_option_chains(ticker, expiry)
+ try:
+ symbol = chain[
+ (chain.strike == strike) & (chain.option_type == op_type)
+ ]["symbol"].values[0]
+ except IndexError:
+ print(f"Strike: {strike}, Option type: {op_type} not not found \n")
+ return
+ else:
+ symbol = ns_parser.chain_id
+
+ df_hist = tradier_model.historical_prices(symbol)
+ df_hist.index = pd.DatetimeIndex(df_hist.index)
+
+ if ns_parser.raw:
+ print(df_hist)
+
+ mc = mpf.make_marketcolors(
+ up="green", down="red", edge="black", wick="black", volume="in", ohlc="i"
+ )
+
+ s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True)
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ mpf.plot(
+ df_hist,
+ type="candle",
+ volume=True,
+ title=f"\n{ticker.upper()} {strike} {op_type} expiring {expiry} Historical",
+ style=s,
+ figratio=(10, 7),
+ figscale=1.10,
+ figsize=(plot_autoscale()),
+ update_width_config=dict(
+ candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0
+ ),
+ )
+
+ print("")
+
+ if ns_parser.export:
+ export_data(
+ ns_parser.export,
+ os.path.dirname(os.path.abspath(__file__)),
+ f"historical_op_{ticker}_{expiry}_{str(strike).replace('.','p')}_{op_type}",
+ df_hist,
+ )
+
+ except Exception as e:
+ print(e, "\n")
+
+ except SystemExit:
+ print("")
diff --git a/gamestonk_terminal/options/yahoo_view.py b/gamestonk_terminal/options/yahoo_view.py
deleted file mode 100644
index 37d995b1c2a..00000000000
--- a/gamestonk_terminal/options/yahoo_view.py
+++ /dev/null
@@ -1,517 +0,0 @@
-""" Volume view """
-__docformat__ = "numpy"
-
-from bisect import bisect_left
-from typing import List
-import argparse
-import pandas as pd
-import matplotlib.pyplot as plt
-import numpy as np
-import seaborn as sns
-
-from gamestonk_terminal.helper_funcs import (
- plot_autoscale,
- check_non_negative,
- parse_known_args_and_warn,
-)
-from gamestonk_terminal import config_plot as cfgPlot
-from gamestonk_terminal import feature_flags as gtff
-
-
-def plot_volume_open_interest(
- other_args: List[str],
- ticker: str,
- exp_date: str,
- last_adj_close_price: float,
- op_calls: pd.DataFrame,
- op_puts: pd.DataFrame,
-):
- """Plot volume 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,
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- 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")
-
-
-def get_calls_puts_maxpain(
- op_calls: pd.DataFrame, op_puts: pd.DataFrame, last_adj_close_price: float
-):
- """Get calls and puts dataframes, and max pain
-
- Parameters
- ----------
- op_calls: pd.DataFrame
- Option data calls
- op_puts: pd.DataFrame
- Option data puts
- last_adj_close_price: float
- Last adjusted closing price
-
- Returns
- ----------
- pd.DataFrame
- Processed calls dataframe
- pd.DataFrame
- Processed puts dataframe
- float
- Max pain
- """
- df_calls = pd.DataFrame()
- df_puts = pd.DataFrame()
- max_pain = 0
-
- if not op_calls.empty:
- # Process Calls Data
- df_calls = op_calls.pivot_table(
- index="strike", values=["volume", "openInterest"], aggfunc="sum"
- ).reindex()
- df_calls["strike"] = df_calls.index
- df_calls["type"] = "calls"
- df_calls["openInterest"] = df_calls["openInterest"]
- df_calls["volume"] = df_calls["volume"]
- df_calls["oi+v"] = df_calls["openInterest"] + df_calls["volume"]
- df_calls["spot"] = round(last_adj_close_price, 2)
-
- 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)
-
- 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 = df_opt[["openInterest_x", "openInterest_y"]].rename(
- columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
- )
- max_pain = get_max_pain(df_opt)
-
- return df_calls, df_puts, max_pain
-
-
-def plot_calls_volume_open_interest(
- other_args: List[str],
- ticker: str,
- exp_date: str,
- last_adj_close_price: float,
- op_calls: pd.DataFrame,
-):
- """Plot calls volume open interest
-
- Parameters
- ----------
- other_args : List[str]
- Command line arguments to be processed with argparse
- ticker : str
- Main ticker to compare income
- exp_date : str
- Expiry date of the option
- last_adj_close_price: float
- Last adjusted closing price
- op_calls: pd.DataFrame
- Option data calls
- """
- parser = argparse.ArgumentParser(
- add_help=False,
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- prog="vcalls",
- description="""
- Plots Calls Volume + Open Interest. [Source: Yahoo Finance]
- """,
- )
- parser.add_argument(
- "-m",
- "--min",
- dest="min_sp",
- type=check_non_negative,
- default=-1,
- help="minimum strike price to consider in the plot.",
- )
- parser.add_argument(
- "-M",
- "--max",
- dest="max_sp",
- type=check_non_negative,
- default=-1,
- help="maximum strike price to consider in the plot.",
- )
-
- try:
- ns_parser = parse_known_args_and_warn(parser, other_args)
- if not ns_parser:
- return
-
- df_calls, _, _ = get_calls_puts_maxpain(
- op_calls, pd.DataFrame(), last_adj_close_price
- )
-
- if ns_parser.min_sp == -1:
- min_strike = 0.75 * last_adj_close_price
- else:
- min_strike = ns_parser.min_sp
-
- if ns_parser.max_sp == -1:
- max_strike = 1.25 * last_adj_close_price
- else:
- max_strike = ns_parser.max_sp
-
- plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
-
- plt.axvline(last_adj_close_price, lw=2)
- plt.bar(
- df_calls["strike"],
- df_calls["volume"] + df_calls["openInterest"],
- color="lightgreen",
- width=0.45,
- )
- plt.bar(df_calls["strike"], df_calls["volume"], color="green", width=0.45)
-
- plt.title(f"{ticker} calls volumes for {exp_date} ")
- plt.legend(["Stock Price", "Open Interest", "Volume"])
- plt.xlabel("Strike Price")
- plt.ylabel("Volume")
- plt.xlim([min_strike, max_strike])
-
- if gtff.USE_ION:
- plt.ion()
- plt.show()
-
- print("")
-
- except Exception as e:
- print(e, "\n")
-
-
-def plot_puts_volume_open_interest(
- other_args: List[str],
- ticker: str,
- exp_date: str,
- last_adj_close_price: float,
- op_puts: pd.DataFrame,
-):
- """Plot puts volume open interest
-
- Parameters
- ----------
- other_args : List[str]
- Command line arguments to be processed with argparse
- ticker : str
- Main ticker to compare income
- exp_date : str
- Expiry date of the option
- last_adj_close_price: float
- Last adjusted closing price
- op_puts: pd.DataFrame
- Option data puts
- """
- parser = argparse.ArgumentParser(
- add_help=False,
- formatter_class=argparse.ArgumentDefaultsHelpFormatter,
- prog="vputs",
- description="""
- Plots Puts Volume + Open Interest. [Source: Yahoo Finance]
- """,
- )
- parser.add_argument(
- "-m",
- "--min",
- dest="min_sp",
- type=check_non_negative,
- default=-1,
- help="minimum strike price to consider in the plot.",
- )
- parser.add_argument(
- "-M",
- "--max",
- dest="max_sp",
- type=check_non_negative,
- default=-1,
- help="maximum strike price to consider in the plot.",
- )
-
- try:
- ns_parser = parse_known_args_and_warn(parser, other_args)
- if not ns_parser:
- return
-
- _, df_puts, _ = get_calls_puts_maxpain(
- pd.DataFrame(), op_puts, last_adj_close_price
- )
-
- if ns_parser.min_sp == -1:
- min_strike = 0.75 * last_adj_close_price
- else:
- min_strike = ns_parser.min_sp
-
- if ns_parser.max_sp == -1:
- max_strike = 1.25 * last_adj_close_price
- else:
- max_strike = ns_parser.max_sp
-
- plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
-
- plt.axvline(last_adj_close_price, lw=2)
- plt.bar(
- df_puts["strike"],
- -df_puts["volume"] - df_puts["openInterest"],
- color="pink",
- width=0.45,
- )
- plt.bar(df_puts["strike"], -df_puts["volume"], color="red", width=0.45)
-
- plt.title(f"{ticker} puts volumes for {exp_date} ")
- plt.legend(["Stock Price", "Open Interest", "Volume"])
- plt.xlabel("Strike Price")
- plt.ylabel("Volume")
- plt.xlim([min_strike, max_strike])
-
- if gtff.USE_ION:
- plt.ion()
- plt.show()
-
- print("")
-
- except Exception as e:
- print(e, "\n")
-
-
-def get_loss_at_strike(strike: float, chain: pd.DataFrame) -> float:
- """Function to get the loss at the given expiry
-
- Parameters
- ----------
- strike: Union[int,float]
- Value to calculate total loss at
- chain: Dataframe:
- Dataframe containing at least strike and openInterest
-
- Returns
- -------
- loss: Union[float,int]
- Total loss
- """
-
- itm_calls = chain[chain.index < strike][["OI_call"]]
- itm_calls["loss"] = (strike - itm_calls.index) * itm_calls["OI_call"]
- call_loss = itm_calls["loss"].sum()
- # The *-1 below is due to a sign change for plotting in the _view code
- itm_puts = chain[chain.index > strike][["OI_put"]]
- itm_puts["loss"] = (itm_puts.index - strike) * itm_puts["OI_put"] * -1
- put_loss = itm_puts.loss.sum()
- loss = call_loss + put_loss
-
- return loss
-
-
-def get_max_pain(chain: pd.DataFrame) -> int:
- """Returns the max pain for a given call/put dataframe
-
- Parameters
- ----------
- chain: DataFrame
- Dataframe to calculate value from
-
- Returns
- -------
- max_pain : int
- Max pain value
- """
-
- strikes = np.array(chain.index)
- if ("OI_call" not in chain.columns) or ("OI_put" not in chain.columns):
- print("Incorrect columns. Unable to parse max pain")
- return np.nan
-
- loss = []
- for price_at_exp in strikes:
- loss.append(get_loss_at_strike(price_at_exp, chain))
-
- chain["loss"] = loss
- max_pain = chain["loss"].idxmin()
-
- return max_pain
diff --git a/gamestonk_terminal/options/yfinance_model.py b/gamestonk_terminal/options/yfinance_model.py
new file mode 100644
index 00000000000..90695917ccf
--- /dev/null
+++ b/gamestonk_terminal/options/yfinance_model.py
@@ -0,0 +1,42 @@
+"""Yfinance options model"""
+__docformat__ = "numpy"
+
+import yfinance as yf
+
+
+def option_expirations(ticker: str):
+ """Get available expiration dates for given ticker
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker to get expirations for
+
+ Returns
+ -------
+ dates: List[str]
+ List of of available expirations
+ """
+ yf_ticker = yf.Ticker(ticker)
+ dates = list(yf_ticker.options)
+ return dates
+
+
+def get_option_chain(ticker: str, expiration: str):
+ """Gets option chain from yf for given ticker and expiration
+
+ Parameters
+ ----------
+ ticker: str
+ Ticker to get options for
+ expiration: str
+ Date to get options for
+
+ Returns
+ -------
+ chains: yf.ticker.Options
+ Options chain
+ """
+ yf_ticker = yf.Ticker(ticker)
+ chains = yf_ticker.option_chain(expiration)
+ return chains
diff --git a/gamestonk_terminal/options/yfinance_view.py b/gamestonk_terminal/options/yfinance_view.py
new file mode 100644
index 00000000000..32a6293d473
--- /dev/null
+++ b/gamestonk_terminal/options/yfinance_view.py
@@ -0,0 +1,367 @@
+"""Yfinance options view"""
+__docformat__ = "numpy"
+
+import argparse
+from bisect import bisect_left
+
+import matplotlib.pyplot as plt
+import seaborn as sns
+import pandas as pd
+import yfinance as yf
+import numpy as np
+
+import gamestonk_terminal.feature_flags as gtff
+import gamestonk_terminal.config_plot as cfp
+from gamestonk_terminal.helper_funcs import (
+ plot_autoscale,
+)
+from gamestonk_terminal.options import op_helpers
+
+
+def plot_oi(
+ calls: pd.DataFrame,
+ puts: pd.DataFrame,
+ ticker: str,
+ expiry: str,
+ ns_parser: argparse.Namespace,
+):
+ """Plot open interest
+
+ Parameters
+ ----------
+ calls: pd.DataFrame
+ Dataframe of call options
+ puts: pd.DataFrame
+ Dataframe of put options
+ ticker: str
+ Ticker
+ expiry: str
+ Expiry date for options
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ current_price = float(yf.Ticker(ticker).info["regularMarketPrice"])
+
+ if ns_parser.min == -1:
+ min_strike = 0.75 * current_price
+ else:
+ min_strike = ns_parser.min
+
+ if ns_parser.max == -1:
+ max_strike = 1.25 * current_price
+ else:
+ max_strike = ns_parser.max
+
+ if ns_parser.calls and ns_parser.puts:
+ print("Both flags selected, please select one", "\n")
+ return
+
+ call_oi = calls.set_index("strike")["openInterest"] / 1000
+ put_oi = puts.set_index("strike")["openInterest"] / 1000
+
+ df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True)
+ df_opt = df_opt.rename(
+ columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
+ )
+
+ max_pain = op_helpers.calculate_max_pain(df_opt)
+ plt.style.use("classic")
+ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
+
+ if not ns_parser.calls:
+ put_oi.plot(
+ x="strike",
+ y="openInterest",
+ label="Puts",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="r",
+ )
+ if not ns_parser.puts:
+ call_oi.plot(
+ x="strike",
+ y="openInterest",
+ label="Calls",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="g",
+ )
+ ax.axvline(
+ current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7
+ )
+ ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.7)
+ ax.grid("on")
+ ax.set_xlabel("Strike Price")
+ ax.set_ylabel("Open Interest (1k) ")
+ ax.set_xlim(min_strike, max_strike)
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}")
+ plt.legend(loc=0)
+ fig.tight_layout(pad=1)
+
+ plt.show()
+ plt.style.use("default")
+ print("")
+
+
+def plot_vol(
+ calls: pd.DataFrame,
+ puts: pd.DataFrame,
+ ticker: str,
+ expiry: str,
+ ns_parser: argparse.Namespace,
+):
+ """Plot volume
+
+ Parameters
+ ----------
+ calls: pd.DataFrame
+ Dataframe of call options
+ puts: pd.DataFrame
+ Dataframe of put options
+ ticker: str
+ Ticker
+ expiry: str
+ Expiry date for options
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ current_price = float(yf.Ticker(ticker).info["regularMarketPrice"])
+
+ if ns_parser.min == -1:
+ min_strike = 0.75 * current_price
+ else:
+ min_strike = ns_parser.min
+
+ if ns_parser.max == -1:
+ max_strike = 1.25 * current_price
+ else:
+ max_strike = ns_parser.max
+
+ if ns_parser.calls and ns_parser.puts:
+ print("Both flags selected, please select one", "\n")
+ return
+
+ call_v = calls.set_index("strike")["volume"] / 1000
+ put_v = puts.set_index("strike")["volume"] / 1000
+ plt.style.use("classic")
+ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
+
+ if not ns_parser.calls:
+ put_v.plot(
+ x="strike",
+ y="volume",
+ label="Puts",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="r",
+ )
+ if not ns_parser.puts:
+ call_v.plot(
+ x="strike",
+ y="volume",
+ label="Calls",
+ ax=ax,
+ marker="o",
+ ls="-",
+ c="g",
+ )
+ ax.axvline(current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7)
+ ax.grid("on")
+ ax.set_xlabel("Strike Price")
+ ax.set_ylabel("Volume (1k) ")
+ ax.set_xlim(min_strike, max_strike)
+
+ if gtff.USE_ION:
+ plt.ion()
+
+ ax.set_title(f"Volume for {ticker.upper()} expiring {expiry}")
+ plt.legend(loc=0)
+ fig.tight_layout(pad=1)
+
+ plt.show()
+ plt.style.use("default")
+ print("")
+
+
+def plot_volume_open_interest(
+ ticker: str,
+ exp_date: str,
+ calls: pd.DataFrame,
+ puts: pd.DataFrame,
+ ns_parser: argparse.Namespace,
+):
+ """Plot volume and open interest
+
+ Parameters
+ ----------
+ ticker : str
+ Main ticker to compare income
+ exp_date : str
+ Expiry date of the option
+ calls: pd.DataFrame
+ Option data calls
+ puts: pd.DataFrame
+ Option data puts
+ ns_parser: argparse.Namespace
+ Parsed namespace
+ """
+
+ current_price = float(yf.Ticker(ticker).info["regularMarketPrice"])
+
+ # Process Calls Data
+ df_calls = 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(current_price, 2)
+
+ df_puts = 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(current_price, 2)
+
+ call_oi = calls.set_index("strike")["openInterest"] / 1000
+ put_oi = puts.set_index("strike")["openInterest"] / 1000
+
+ df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True)
+ df_opt = df_opt.rename(
+ columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
+ )
+
+ max_pain = op_helpers.calculate_max_pain(df_opt)
+
+ 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=cfp.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, current_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(3)
+
+ # 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()
+ plt.style.use("default")
+ print("")
diff --git a/terminal.py b/terminal.py
index f42dd927fb0..0b608a5d76f 100644
--- a/terminal.py
+++ b/terminal.py
@@ -432,18 +432,7 @@ Contexts:
def call_op(self, _):
"""Process op command"""
- if self.interval == "1440min":
- return op_controller.menu(
- self.ticker,
- self.stock,
- )
-
- df_stock = yf.download(self.ticker, start=self.start, progress=False)
-
- return op_controller.menu(
- self.ticker,
- df_stock,
- )
+ return op_controller.menu(self.ticker)
def call_pred(self, _):
"""Process pred command"""