Options menu updates (#560)

* Add `calculator` function to show pnl for calls and puts

* calculator improvement

* breaking up op into different submenus

* Yfinance functions/plots added

* Refactored Tradier menu into new controller

* Added tradier historical options and fix crypto candles

* Clean up files and get rid of selenium dependecne

* ReadME 1

* calculator plot

* Redefine main menu structure to get rid of sub menus.

* Merge main + update gst readme

* Add coloring to chain command and update readme images

* Add historical greeks

* added ion

* Fix up basic calculator

* Clean up error messages and yf defaults when tradier key not present.  Extend `exp` to allow date entry

* add new line
This commit is contained in:
jmaslek
2021-07-25 11:30:38 -04:00
committed by GitHub
parent f89fcc72a3
commit 419c3691db
17 changed files with 2218 additions and 896 deletions

0
exports/options/.gitkeep vendored Normal file
View File

View File

@@ -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

View File

@@ -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),

View File

@@ -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(

View File

@@ -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 <a name="disp"></a>
@@ -35,7 +43,6 @@ View available presets under [presets folder](/gamestonk_terminal/options/preset
<img width="979" alt="Captura de ecrã 2021-06-27, às 02 32 21" src="https://user-images.githubusercontent.com/25267873/123530365-59fac080-d6f1-11eb-85e1-536f7e927308.png">
## scr <a name="scr"></a>
```text
@@ -50,67 +57,64 @@ Day Low; SMDH: Regular Market Day High; LU: Last Trade Date; LC: Last Crawl; ITM
<img width="1220" alt="Captura de ecrã 2021-06-27, às 02 31 28" src="https://user-images.githubusercontent.com/25267873/123530368-5e26de00-d6f1-11eb-9a23-0b481b5efae1.png">
## load <a name="load"></a>
Load new ticker
```python
usage: load [-t Ticker]
```
* -t/--ticker: Ticker to load. Flag is optional, as `load aapl` will also work
## info <a name="info"></a>
This scrapes the options information from barchart.com/stocks overview. This includes information such as Historical Volatility and IV Rank.
````
usage: info
````
<img width="989" alt="opinfo" src="https://user-images.githubusercontent.com/25267873/115787028-80cb0c80-a3b9-11eb-97a4-ca208aed3be8.png">
## calc <a name="calc"></a>
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
![calc](https://user-images.githubusercontent.com/18151143/126883982-c54cb5ec-7eb2-41c1-812e-75e830e3703d.png)
## exp <a name="exp"></a>
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
<img width="955" alt="exp" src="https://user-images.githubusercontent.com/25267873/115161875-f6706900-a097-11eb-8566-bb7b408856a5.png">
Running `exp` will show available option expirations
```python
(✨) (op)> exp
## voi <a name="voi"></a>
```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.
![voinio](https://user-images.githubusercontent.com/25267873/115161878-f7a19600-a097-11eb-889b-e9cc945f174d.png)
## vcalls <a name="vcalls"></a>
```text
usage: vcalls [-m MIN_SP] [-M MAX_SP]
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.
![vcalls](https://user-images.githubusercontent.com/25267873/115161874-f5d7d280-a097-11eb-93e2-3a2b20292f09.png)
## vputs <a name="vputs"></a>
```text
usage: vputs [-m MIN_SP] [-M MAX_SP]
```
Plots Puts Volume + Open Interest. [Source: Yahoo Finance]
* -m : Minimum strike price to consider in the plot.
* -M : Maximum strike price to consider in the plot.
![vputs](https://user-images.githubusercontent.com/25267873/115161873-f40e0f00-a097-11eb-8334-3d4f14b56766.png)
## chains <a name="chains"></a>
````
usage: chains [--calls] [--puts] [-m MIN_SP] [-M MAX_SP]
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}.
![chains](https://user-images.githubusercontent.com/18151143/126855845-4f156b6d-a368-4e66-8dc9-b971792128d8.png)
## oi <a name="oi"></a>
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.
![oi](https://user-images.githubusercontent.com/18151143/126855897-87eadd56-cca0-4eb0-8e55-6b7035762cbc.png)
## vol <a name="vol"></a>
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.
![vol](https://user-images.githubusercontent.com/18151143/126855840-8fc3446d-081e-4961-9925-eb6c6e51c136.png)
## voi <a name="voi"></a>
```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.
![voi](https://user-images.githubusercontent.com/18151143/126855842-927ca7ff-78ca-46ce-9cc0-2a8dff4593af.png)
## hist <a name="hist"></a>
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`
![hist](https://user-images.githubusercontent.com/18151143/126855841-6884b19b-63c8-4746-a63f-82ad1a523c0c.png)
<img width="948" alt="chains" src="https://user-images.githubusercontent.com/25267873/115161876-f708ff80-a097-11eb-8073-195979862a45.png">
## gr_hist <a name="gr_hist"></a>
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 <a name="info"></a>
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.
<img width="989" alt="opinfo" src="https://user-images.githubusercontent.com/25267873/115787028-80cb0c80-a3b9-11eb-97a4-ca208aed3be8.png">
```python
(✨) (op)> gr_hist -s 145 --put -g delta
```
![gr_hist](https://user-images.githubusercontent.com/18151143/126881002-c516d94b-5c87-43b1-9e16-dce304795e1c.png)

View File

View File

@@ -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())

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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("")

View File

@@ -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

View File

@@ -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

View File

@@ -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("")

View File

@@ -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"""