import streamlit as st import numpy as np import pandas as pd from datetime import datetime, timedelta import mibian from yahoofinancials import YahooFinancials import matplotlib.pyplot as plt from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def get_target_date(experations, min_days, max_days): today = datetime.today() experations_dates = [datetime.strptime(i.text, '%b %d, %Y') for i in experations if len(i.text)>5 and i.text[-4:]=='2024'] start_date = today + timedelta(days=min_days) end_date = today + timedelta(days=max_days) filtered_dates = [date for date in experations_dates if start_date <= date <= end_date] return filtered_dates[0] if filtered_dates else None def calculate_option_value(row, stock_price, risk_free_rate, maturity): strike = row['Strike'] volatility = row['IV'] option = mibian.BS([stock_price, strike, risk_free_rate, maturity], volatility=volatility) return [ option.callPrice, option.callDelta, option.callTheta, option.vega, option.gamma, option.callRho ] def main(): st.title("Options Analysis App") # User inputs stock = st.text_input("Enter stock symbol (e.g., AAPL):", "AAPL") option_type = st.selectbox("Option type:", ["Calls", "Puts"]) min_days = st.number_input("Minimum days to expiration:", value=60, min_value=0, max_value=365) max_days = st.number_input("Maximum days to expiration:", value=80, min_value=0, max_value=365) num_strikes = st.number_input("Number of strikes to display (above and below current price):", value=3, min_value=1, max_value=10) use_custom_risk_free_rate = st.checkbox("Use custom risk-free rate") custom_risk_free_rate = st.number_input("Custom risk-free rate (%):", value=4.0, min_value=0.0, max_value=20.0, disabled=not use_custom_risk_free_rate) if st.button("Analyze Options"): try: # Get current date today = datetime.today() # Get risk-free rate if use_custom_risk_free_rate: risk_free_rate = custom_risk_free_rate / 100 else: yahoo_financials_treasuries = YahooFinancials('^TNX') risk_free_rate = round(yahoo_financials_treasuries.get_current_price()/100, 4) st.write(f"Risk-free rate: {risk_free_rate:.2%}") # Set up Selenium with headless Chrome chrome_options = Options() chrome_options.add_argument("--headless") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") driver = webdriver.Chrome(options=chrome_options) url = f"https://finance.yahoo.com/quote/{stock}/options/?straddle=false" driver.get(url) # Wait for the expiration button to be clickable wait = WebDriverWait(driver, 20) experations_btn = wait.until(EC.element_to_be_clickable((By.XPATH, '//button[contains(@class, "toggleButton")]'))) experations_btn.click() experations = driver.find_elements(By.XPATH, '//span[contains(@class, "C($linkColor)")]') target_date_dt = get_target_date(experations, min_days, max_days) if not target_date_dt: st.error(f"Could not find a suitable expiration date between {min_days} and {max_days} days from now. Please try different date ranges.") driver.quit() return # Desired date as a string target_date_str = target_date_dt.strftime('%b %d, %Y') # Loop through date elements to find the desired date and click on it date_clicked = False for element in experations: if element.text == target_date_str: element.click() date_clicked = True break if not date_clicked: st.error(f"Could not find the expiration date {target_date_str}. Please try again.") driver.quit() return # Wait for the options table to load wait.until(EC.presence_of_element_located((By.TAG_NAME, "table"))) tables = driver.find_elements(By.TAG_NAME, "table") stock_price = float(driver.find_element(By.XPATH, '//fin-streamer[@data-symbol="' + stock + '"]').text) if len(tables) < 1: st.error("Could not find options data. Please try again.") driver.quit() return table_index = 0 if option_type == "Calls" else 1 options = [i.text.split() for i in tables[table_index].find_elements(By.TAG_NAME,'tr')] columns_names = ['Contract','Date','Time','ET','Strike','Price','Bid','Ask','Change','% Change','Volume','Open Interest','Implied Volatility'] options_df = pd.DataFrame(options[1:], columns=columns_names) options_df['Strike'] = options_df['Strike'].astype(float) lower = options_df[options_df['Strike'] < stock_price].tail(num_strikes)[::-1] higher = options_df[options_df['Strike'] > stock_price].head(num_strikes)[::-1] options_df = pd.concat([higher, lower]) options_df.reset_index(drop=True, inplace=True) maturity = (target_date_dt - today).days options_df["IV"] = options_df["Implied Volatility"].str.strip("%").astype(float) if option_type == "Calls": options_df[['BSM Price','Delta', 'Theta','Vega',"Gamma",'Rho']] = options_df[['Strike','IV']].apply( lambda row: calculate_option_value(row, stock_price, risk_free_rate, maturity), axis=1, result_type='expand' ) else: # For puts, we need to adjust the calculation options_df[['BSM Price','Delta', 'Theta','Vega',"Gamma",'Rho']] = options_df[['Strike','IV']].apply( lambda row: calculate_option_value(row, stock_price, risk_free_rate, maturity), axis=1, result_type='expand' ) options_df['Delta'] = options_df['Delta'] - 1 # Adjust delta for puts options_df.drop(columns=["Date","Time","ET","Change","% Change","Implied Volatility"], inplace=True) options_df['Ask'] = options_df['Ask'].astype(float) options_df["Under"] = np.where(options_df['Ask'] < options_df['BSM Price'], True, False) st.write(f"Analysis for {stock} {option_type.lower()} expiring on {target_date_str}") st.write(f"Current stock price: ${stock_price}") st.dataframe(options_df) except Exception as e: st.error(f"An error occurred: {str(e)}") finally: # Close the browser if 'driver' in locals(): driver.quit() if __name__ == "__main__": main()