Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add chains via yahoo finance #2176

Merged
merged 7 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions data_sources_default.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
8 changes: 4 additions & 4 deletions openbb_terminal/stocks/options/op_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
28 changes: 21 additions & 7 deletions openbb_terminal/stocks/options/options_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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:
Expand Down
98 changes: 98 additions & 0 deletions openbb_terminal/stocks/options/yfinance_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading