Spaces:
Runtime error
Runtime error
| 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() |