Skip to content

Commit

Permalink
Add chains via yahoo finance (#2176)
Browse files Browse the repository at this point in the history
* Add chains via yahoo finance

* Fix existing tradier  test

* Update chains hugo

* test options controller

* business_insider_view tests

* test yfinance view
  • Loading branch information
jmaslek authored Jul 25, 2022
1 parent 52e46d3 commit 66f05b8
Showing 30 changed files with 35,492 additions and 26 deletions.
20 changes: 12 additions & 8 deletions data_sources_default.json
Original file line number Diff line number Diff line change
@@ -34,24 +34,28 @@
},
"options": {
"load": [
"tradier",
"yf"
"yf",
"tradier"
],
"hist": [
"chartexchange",
"tradier"
],
"vol": [
"tradier",
"yf"
"yf",
"tradier"
],
"voi": [
"tradier",
"yf"
"yf",
"tradier"
],
"oi": [
"tradier",
"yf"
"yf",
"tradier"
],
"chains": [
"yf",
"tradier"
]
},
"disc": {
8 changes: 4 additions & 4 deletions openbb_terminal/stocks/options/op_helpers.py
Original file line number Diff line number Diff line change
@@ -204,7 +204,7 @@ def Premium(self):
# 1st order greeks

def Delta(self):
dfq = e ** (-self.div_cont * self.exp_time)
dfq = np.exp(-self.div_cont * self.exp_time)
if self.Type == 1:
return dfq * norm.cdf(self.d1)
return dfq * (norm.cdf(self.d1) - 1)
@@ -214,15 +214,15 @@ def Vega(self):
return (
0.01
* self.price
* e ** (-self.div_cont * self.exp_time)
* np.exp(-self.div_cont * self.exp_time)
* norm.pdf(self.d1)
* self.exp_time**0.5
)

def Theta(self):
"""Theta for 1 day change"""
df = e ** -(self.risk_free * self.exp_time)
dfq = e ** (-self.div_cont * self.exp_time)
df = np.exp(-self.risk_free * self.exp_time)
dfq = np.exp(-self.div_cont * self.exp_time)
tmptheta = (1.0 / 365.0) * (
-0.5
* self.price
28 changes: 21 additions & 7 deletions openbb_terminal/stocks/options/options_controller.py
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
EXPORT_ONLY_FIGURES_ALLOWED,
EXPORT_ONLY_RAW_DATA_ALLOWED,
valid_date,
get_ordered_list_sources,
)
from openbb_terminal.menu import session
from openbb_terminal.parent_classes import BaseController
@@ -126,6 +127,8 @@ def __init__(self, ticker: str, queue: List[str] = None):
else:
self.expiry_dates = []

self.default_chain = get_ordered_list_sources(f"{self.PATH}chains")[0]

if session and obbff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.controller_choices}
choices["unu"]["-s"] = {c: {} for c in self.unu_sortby_choices}
@@ -731,28 +734,39 @@ def call_chains(self, other_args: List[str]):
dest="to_display",
default=tradier_model.default_columns,
type=tradier_view.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. E.g. 'bid,ask,strike' ",
help="(tradier only) 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. E.g. 'bid,ask,strike' ",
)
ns_parser = self.parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
if self.ticker:
if self.selected_date:
if TRADIER_TOKEN != "REPLACE_ME": # nosec
tradier_view.display_chains(
if ns_parser.source == "tradier":
if TRADIER_TOKEN != "REPLACE_ME": # nosec
tradier_view.display_chains(
ticker=self.ticker,
expiry=self.selected_date,
to_display=ns_parser.to_display,
min_sp=ns_parser.min_sp,
max_sp=ns_parser.max_sp,
calls_only=ns_parser.calls,
puts_only=ns_parser.puts,
export=ns_parser.export,
)
else:
console.print("TRADIER TOKEN not supplied. \n")
if ns_parser.source == "yf":
yfinance_view.display_chains(
ticker=self.ticker,
expiry=self.selected_date,
to_display=ns_parser.to_display,
min_sp=ns_parser.min_sp,
max_sp=ns_parser.max_sp,
calls_only=ns_parser.calls,
puts_only=ns_parser.puts,
export=ns_parser.export,
)
else:
console.print("TRADIER TOKEN not supplied. \n")
else:
console.print("No expiry loaded. First use `exp {expiry date}`\n")
else:
98 changes: 98 additions & 0 deletions openbb_terminal/stocks/options/yfinance_model.py
Original file line number Diff line number Diff line change
@@ -8,12 +8,110 @@
import pandas as pd
import yfinance as yf

from openbb_terminal.stocks.options.op_helpers import Option
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console

logger = logging.getLogger(__name__)


# pylint: disable=W0640
@log_start_end(log=logger)
def get_full_option_chain(
ticker: str, expiration: str, calls: bool = True, puts: bool = True
) -> pd.DataFrame:
"""Get full option chains with calculated greeks
Parameters
----------
ticker: str
Stock ticker
expiration: str
Expiration date for chain in format YYY-mm-dd
calls: bool
Flag to get calls
puts: bool
Flag to get puts
Returns
-------
pd.DataFrame
DataFrame of option chain. If both calls and puts
"""
try:
yf_ticker = yf.Ticker(ticker)
options = yf_ticker.option_chain(expiration)
except ValueError:
console.print(f"[red]{ticker} options for {expiration} not found.[/red]")
return pd.DataFrame()

last_price = yf_ticker.info["regularMarketPrice"]

# Columns we want to get
yf_option_cols = [
"strike",
"lastPrice",
"bid",
"ask",
"volume",
"openInterest",
"impliedVolatility",
]
# Get call and put dataframes if the booleans are true
put_df = options.puts[yf_option_cols].copy() if puts else pd.DataFrame()
call_df = options.calls[yf_option_cols].copy() if calls else pd.DataFrame()
# so that the loop below doesn't break if only one call/put is supplied
df_list, option_factor = [], []
if puts:
df_list.append(put_df)
option_factor.append(-1)
if calls:
df_list.append(call_df)
option_factor.append(1)
# Add in greeks to each df
# Time to expiration:
dt = (datetime.strptime(expiration, "%Y-%m-%d") - datetime.now()).seconds / (
60 * 60 * 24
)
# Note the way the Option class is defined, put has a -1 input and call has a +1 input
for df, option_type in zip(df_list, option_factor):
df["Delta"] = df.apply(
lambda x: Option(
last_price, x.strike, 0.03, 0, dt, x.impliedVolatility, option_type
).Delta(),
axis=1,
)
df["Gamma"] = df.apply(
lambda x: Option(
last_price, x.strike, 0.03, 0, dt, x.impliedVolatility, option_type
).Gamma(),
axis=1,
)
df["Theta"] = df.apply(
lambda x: Option(
last_price, x.strike, 0.03, 0, dt, x.impliedVolatility, option_type
).Theta(),
axis=1,
)

# Create our merged dataframe. If only puts and/or calls are wanted, no merging needed
if not put_df.empty and call_df.empty:
options_df = put_df.copy()
if not call_df.empty and put_df.empty:
options_df = call_df.copy()
if not put_df.empty and not call_df.empty:
# Join these guys on strike. Do an outer join to get all strikes.
options_df = pd.merge(
left=call_df,
right=put_df,
on="strike",
how="outer",
suffixes=["_call", "_put"],
)

return options_df


@log_start_end(log=logger)
def option_expirations(ticker: str):
"""Get available expiration dates for given ticker
Loading

0 comments on commit 66f05b8

Please sign in to comment.