diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..023ea39a Binary files /dev/null and b/.DS_Store differ diff --git a/Dockerfile b/Dockerfile index 40cede8a..c9567bdf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,18 @@ -FROM python:3.7 +FROM python:3.9 COPY . /app WORKDIR /app -RUN pip install -r Requirements.txt +RUN pip install --no-cache-dir -U pip && \ + pip install --no-cache-dir -r Requirements.txt EXPOSE 8080 -CMD streamlit run --server.port 8080 --server.enableCORS false streamlit_app.py \ No newline at end of file +CMD ["streamlit", "run", "--server.port", "8080", "--server.address", "0.0.0.0", "streamlit_app.py"] + +# build command +# docker build -t options-pricing:latest . + +# run command +# docker run -p 8080:8080 options-pricing:latest \ No newline at end of file diff --git a/README.md b/README.md index 585ab74d..f301fdfa 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,116 @@ # Option Pricing Models -## Introduction -This repository represents simple web app for calculating option prices (European Options). It uses three different methods for option pricing: -1. Black-Scholes model -2. Monte Carlo simulation -3. Binomial model +## Introduction +This repository contains a simple web app for calculating European option prices using three different methods: -Each model has various parameters that user needs to import: +1. Black-Scholes model +2. Monte Carlo simulation +3. Binomial model -- Ticker -- Strike price -- Expiry date -- Risk-free rate -- Volatility +The app is implemented in Python 3.9 and uses the Streamlit library for visualization. -Option pricing models are implemented in [Python 3.7](https://www.python.org/downloads/release/python-377/). Latest spot price, for specified ticker, is fetched from Yahoo Finance API using [pandas-datareader](https://pandas-datareader.readthedocs.io/en/latest/). Visualization of the models through simple web app is implemented using [streamlit](https://www.streamlit.io/) library. +## Option Pricing Methods -When data is fetched from Yahoo Finance API using pandas-datareader, it's cached with [request-cache](https://github.com/reclosedev/requests-cache) library is sqlite db, so any subsequent testing and changes in model parameters with same underlying instrument won't result in duplicated request for fethcing already fetched data. +### 1. Black-Scholes Model +A mathematical model used to calculate the theoretical price of European-style options, based on factors like current stock price, strike price, time to expiration, risk-free rate, and volatility. -This implementation was done as project work on the course [Special Functions (Applied Mathematics)](https://www.etf.bg.ac.rs/en/fis/karton_predmeta/13M081SPEF-2013) on Master's degree in Software Engineering. +### 2. Monte Carlo Simulation +A probabilistic method that uses random sampling to estimate option prices by simulating multiple possible price paths of the underlying asset. -## Streamlit web app +### 3. Binomial Model +A discrete-time model that represents the evolution of the underlying asset's price as a binomial tree, allowing for the calculation of option prices at different time steps. -1. Black-Scholes model -![black-scholes-demo](./demo/streamlit-webapp-BS.gif) +## Features -2. Monte Carlo Option Pricing -![monte-carlo-demo](./demo/streamlit-webapp-MC.gif) +- Fetches latest stock price data from Yahoo Finance API using pandas-datareader +- Caches data using requests-cache to avoid duplicate API calls +- Allows users to input various parameters: + - Strike price + - Risk-free rate (%) + - Sigma (Volatility) (%) + - Exercise date +- Calculates option prices based on user inputs +- Provides a user-friendly interface for testing different scenarios -3. Binomial model -![binomial-tree-demo](./demo/streamlit-webapp-BC.gif) +## Project Structure +- `demo/`: Contains GIF files demonstrating the Streamlit app +- `option_pricing/`: Python package containing model implementations +- `streamlit_app.py`: Main script for the Streamlit web app +- `Requirements.txt`: List of required Python packages +- `Dockerfile`: Configuration for running the app in a Docker container -## Project structure -In this repository you will find: +## How to Run the App -- demo directory - contains .gif files as example of streamlit app. -- option_pricing package - python package where models are implemented. -- option_pricing_test.py script - example code for testing option pricing models (without webapp). -- streamlit_app.py script - web app for testing models using streamlit library. -- Requirements.txt file - python pip package requirements. -- Dockerfile file - for running containerized streamlit web app. -- app.yaml file - for deploying dockerized app on GCP(Google Cloud Platform). +### Using Docker Locally +The easiest way to run the app is using Docker. Make sure you have Docker installed on your machine before proceeding. +1. Navigate to the repository directory in your terminal. -## How to run code? -You can use simple streamlit web app to test option pricing models either by manually setting up python environment and running streamlit app or by running docker container. +2. Build the Docker image: + ``` + docker build -t options-pricing:latest . + ``` -### **1. Running docker container** -Dockerfile has exposed 8080 (default web browser port), so when you deploy it to some cloud provider, it would be automatically possible to access your recently deployed webb app through browser. +3. Verify the image was built successfully: + ``` + docker image ls + ``` -***1.1 Running docker container locally*** -First you will need to build the docker image (this may take a while, because it's downloading all the python libraries from Requirements.txt file) and specify tag e.g. option-pricing:initial: -`docker build -t option-pricing:initial .` +4. Run the Docker container: + ``` + docker run -p 8080:8080 options-pricing:latest + ``` -When image is built, you can execute following command, that lists all docker images, to check if image was successfully build: -`docker image ls` +5. Access the app in your web browser at: + ``` + http://0.0.0.0:8080/ + ``` -Now, you can run docker container with following command: -`docker run -p 8080:8080 option-pricing:initial` +### Using Google Cloud -When you see output in command line that streamlit app is running on port 8080, you can access it with browser: -`http://localhost:8080/` +To deploy the Docker container to Google Cloud Platform (GCP), follow these steps: +1. Prerequisites: + - Have a Google account + - Create a project on Google Cloud Console + - Set up billing for your project (be aware of GCP's pricing structure) + - Install and set up Google Cloud SDK -***1.2 Deploying docker container to Google Cloud Platform*** -Before you deploy to GCP, please make sure you have google acount, created project on Google Developer Console, set up the billing method (please make sure you understand how Google is charging for hosting!) and downloaded [Google Cloud SDK](https://cloud.google.com/sdk/docs/quickstarts). +2. Verify and set your GCP project: + - Check the current project: + ``` + gcloud config get-value project + ``` + - Set a different project if needed: + ``` + gcloud config set project YOUR_PROJECT_NAME + ``` -To chech which project (one from list of projects created on Google Developer Console) is currently used with Google Cloud SDK, use: -`gcloud config get-value project` +3. Deploy the application: + - Run the following command (uses the app.yaml file in your project): + ``` + gcloud app deploy + ``` + - Select the nearest server location when prompted + - Wait for the deployment process to complete -To chage/set project use: -`gcloud config set project project-name` +4. Access your web app: + - After deployment, you'll receive a URL for your app + - The URL format will be: https://YOUR_PROJECT_NAME.REGION.r.appspot.com/ + - You can also find this URL in the Google Cloud Console -When you have correct project in use for Cloud SDK, now you can deploy it using following command (it will use .yaml file from project structure as instructiong on how to deploy it): -`gcloud app deploy` -After choosing neared physical server to host your app, you will have to wait a bit for whole process to finish. Once everything is over, you will be prompted with a link to your web app (you can check that on Developer console as well). -Link for your webb app will be something like this: `https://project-name.ey.r.appspot.com/`. +Note: Ensure you understand GCP's pricing before deploying to avoid unexpected charges. -### **2. Running streamlit app locally with python** -It is recommended that you create new [virtual environment](https://docs.python.org/3.7/tutorial/venv.html): -`python3 -m venv option-pricing` +## Streamlit Web App Demonstrations -Then you would need to activate that newly created python environment: - -* On Windows: -`option-pricing\Scripts\activate.bat` -* On Linux: -`source option-pricing/bin/activate` - -Once you have your python environment activated, first you would need to download all necessary python modules with pip. There is Requirements.txt file in scr directory. You can use the following command to automatically download all dependencies: -`pip install -r Requirements.txt` - -When the download is completed, you can run streamlit app with: -`streamlit run streamlit_app.py` - - - +### Black-Scholes Model +![black-scholes-demo](media/Black_Scholes_Model.gif) +### Monte Carlo Option Pricing +![monte-carlo-demo](media/Monte_Carlo_Option_Pricing.gif) +### Binomial Model +![binomial-tree-demo](media/Binomial_Model.gif) +By following these instructions, you can easily set up and explore the option pricing models using the Streamlit web app. Feel free to experiment with different parameters and see how they affect the calculated option prices. \ No newline at end of file diff --git a/Requirements.txt b/Requirements.txt index c5d2a4bc..355c4f80 100644 --- a/Requirements.txt +++ b/Requirements.txt @@ -1,19 +1,6 @@ -certifi==2020.6.20 -chardet==3.0.4 -cycler==0.10.0 -idna==2.10 -kiwisolver==1.2.0 -lxml==4.5.2 -matplotlib==3.2.2 -numpy==1.19.0 -pandas==1.0.5 -pandas-datareader==0.9.0 -pyparsing==2.4.7 -python-dateutil==2.8.1 -pytz==2020.1 -requests==2.24.0 -requests-cache==0.5.2 -scipy==1.5.1 -six==1.15.0 -streamlit==0.62.1 -urllib3==1.25.9 +streamlit==1.29.0 +matplotlib==3.7.4 +numpy==1.26.2 +pandas==2.1.4 +scipy==1.11.4 +yfinance==0.2.31 \ No newline at end of file diff --git a/demo/streamlit-webapp-BC.gif b/demo/streamlit-webapp-BC.gif deleted file mode 100644 index 5da4923c..00000000 Binary files a/demo/streamlit-webapp-BC.gif and /dev/null differ diff --git a/demo/streamlit-webapp-BS.gif b/demo/streamlit-webapp-BS.gif deleted file mode 100644 index 6dfc679a..00000000 Binary files a/demo/streamlit-webapp-BS.gif and /dev/null differ diff --git a/demo/streamlit-webapp-MC.gif b/demo/streamlit-webapp-MC.gif deleted file mode 100644 index 8c1b797f..00000000 Binary files a/demo/streamlit-webapp-MC.gif and /dev/null differ diff --git a/media/Binomial_Model.gif b/media/Binomial_Model.gif new file mode 100644 index 00000000..3801cb58 Binary files /dev/null and b/media/Binomial_Model.gif differ diff --git a/media/Black_Scholes_Model.gif b/media/Black_Scholes_Model.gif new file mode 100644 index 00000000..6cbd4be6 Binary files /dev/null and b/media/Black_Scholes_Model.gif differ diff --git a/media/Monte_Carlo_Option_Pricing.gif b/media/Monte_Carlo_Option_Pricing.gif new file mode 100644 index 00000000..66236db2 Binary files /dev/null and b/media/Monte_Carlo_Option_Pricing.gif differ diff --git a/option_pricing.py b/option_pricing.py new file mode 100644 index 00000000..5dffe288 --- /dev/null +++ b/option_pricing.py @@ -0,0 +1,28 @@ +class BlackScholesModel: + # ... (existing code) ... + + def get_calculation_steps(self): + steps = { + "Input Parameters": { + "Spot Price": self.S, + "Strike Price": self.K, + "Time to Maturity (days)": self.T, + "Risk-free Rate": self.r, + "Volatility": self.sigma + }, + "Intermediate Calculations": { + "d1": self.d1, + "d2": self.d2, + "N(d1)": self.N_d1, + "N(d2)": self.N_d2, + "N(-d1)": self.N_minus_d1, + "N(-d2)": self.N_minus_d2 + }, + "Final Calculations": { + "Call Option Price": self.call_price, + "Put Option Price": self.put_price + } + } + return steps + +# Similarly, add get_calculation_steps() methods to MonteCarloPricing and BinomialTreeModel classes \ No newline at end of file diff --git a/option_pricing/base.py b/option_pricing/base.py index 8e7bad68..416aa017 100644 --- a/option_pricing/base.py +++ b/option_pricing/base.py @@ -1,5 +1,5 @@ from enum import Enum -from abc import ABC, abstractclassmethod +from abc import ABC, abstractmethod class OPTION_TYPE(Enum): CALL_OPTION = 'Call Option' @@ -17,12 +17,14 @@ def calculate_option_price(self, option_type): else: return -1 - @abstractclassmethod - def _calculate_call_option_price(self): + @classmethod + @abstractmethod + def _calculate_call_option_price(cls): """Calculates option price for call option.""" pass - @abstractclassmethod - def _calculate_put_option_price(self): + @classmethod + @abstractmethod + def _calculate_put_option_price(cls): """Calculates option price for put option.""" pass \ No newline at end of file diff --git a/option_pricing/ticker.py b/option_pricing/ticker.py index b16dc64f..16351f89 100644 --- a/option_pricing/ticker.py +++ b/option_pricing/ticker.py @@ -1,92 +1,51 @@ -# Standard library imports import datetime - -# Third party imports -import requests_cache +import yfinance as yf import matplotlib.pyplot as plt -from pandas_datareader import data as wb - +import pandas as pd class Ticker: - """Class for fetcing data from yahoo finance.""" - @staticmethod - def get_historical_data(ticker, start_date=None, end_date=None, cache_data=True, cache_days=1): - """ - Fetches stock data from yahoo finance. Request is by default cashed in sqlite db for 1 day. - - Params: - ticker: ticker symbol - start_date: start date for getting historical data - end_date: end date for getting historical data - cache_date: flag for caching fetched data into slqite db - cache_days: number of days data will stay in cache - """ + def get_historical_data(ticker, start_date=None, end_date=None): try: - # initializing sqlite for caching yahoo finance requests - expire_after = datetime.timedelta(days=1) - session = requests_cache.CachedSession(cache_name='cache', backend='sqlite', expire_after=expire_after) - - # Adding headers to session - session.headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0', 'Accept': 'application/json;charset=utf-8'} # noqa + if start_date is None: + start_date = datetime.datetime.now() - datetime.timedelta(days=365) + if end_date is None: + end_date = datetime.datetime.now() + + stock = yf.Ticker(ticker) + data = stock.history(start=start_date, end=end_date) - if start_date is not None and end_date is not None: - data = wb.DataReader(ticker, data_source='yahoo', start=start_date, end=end_date, session=session) - else: - data = wb.DataReader(ticker, data_source='yahoo', session=session) #['Adj Close'] - if data is None: - return None + if data.empty: + raise ValueError(f"No data returned for ticker {ticker}") return data except Exception as e: - print(e) - return None + raise Exception(f"Error fetching data for ticker {ticker}: {str(e)}") @staticmethod def get_columns(data): - """ - Gets dataframe columns from previously fetched stock data. - - Params: - data: dataframe representing fetched data - """ - if data is None: - return None - return [column for column in data.columns] + if not isinstance(data, pd.DataFrame): + raise ValueError("Input must be a pandas DataFrame") + return list(data.columns) @staticmethod def get_last_price(data, column_name): - """ - Returns last available price for specified column from already fetched data. - - Params: - data: dataframe representing fetched data - column_name: name of the column in dataframe - """ - if data is None or column_name is None: - return None - if column_name not in Ticker.get_columns(data): - return None - return data[column_name].iloc[len(data) - 1] - + if not isinstance(data, pd.DataFrame): + raise ValueError("Input must be a pandas DataFrame") + if column_name not in data.columns: + raise ValueError(f"Column '{column_name}' not found in the DataFrame") + return data[column_name].iloc[-1] @staticmethod def plot_data(data, ticker, column_name): - """ - Plots specified column values from dataframe. + if not isinstance(data, pd.DataFrame): + raise ValueError("Input must be a pandas DataFrame") + if column_name not in data.columns: + raise ValueError(f"Column '{column_name}' not found in the DataFrame") - Params: - data: dataframe representing fetched data - column_name: name of the column in dataframe - """ - try: - if data is None: - return - data[column_name].plot() - plt.ylabel(f'{column_name}') - plt.xlabel('Date') - plt.title(f'Historical data for {ticker} - {column_name}') - plt.legend(loc='best') - plt.show() - except Exception as e: - print(e) - return \ No newline at end of file + plt.figure(figsize=(10, 6)) + data[column_name].plot() + plt.ylabel(column_name) + plt.xlabel('Date') + plt.title(f'Historical data for {ticker} - {column_name}') + plt.legend(loc='best') + return plt \ No newline at end of file diff --git a/streamlit_app.py b/streamlit_app.py index 267cb5d5..ebdb4272 100644 --- a/streamlit_app.py +++ b/streamlit_app.py @@ -1,22 +1,32 @@ -# Standart python imports +import streamlit as st from enum import Enum from datetime import datetime, timedelta - -# Third party imports -import streamlit as st - -# Local package imports +import yfinance as yf from option_pricing import BlackScholesModel, MonteCarloPricing, BinomialTreeModel, Ticker +import json class OPTION_PRICING_MODEL(Enum): BLACK_SCHOLES = 'Black Scholes Model' MONTE_CARLO = 'Monte Carlo Simulation' BINOMIAL = 'Binomial Model' -@st.cache +@st.cache_data def get_historical_data(ticker): - """Getting historical data for speified ticker and caching it with streamlit app.""" - return Ticker.get_historical_data(ticker) + try: + data = Ticker.get_historical_data(ticker) + return data + except Exception as e: + st.error(f"Error fetching data for {ticker}: {str(e)}") + return None + +@st.cache_data +def get_current_price(ticker): + try: + data = yf.Ticker(ticker).history(period="1d") + return data['Close'].iloc[-1] + except Exception as e: + st.error(f"Error fetching current price for {ticker}: {str(e)}") + return None # Ignore the Streamlit warning for using st.pyplot() st.set_option('deprecation.showPyplotGlobalUse', False) @@ -33,99 +43,208 @@ def get_historical_data(ticker): if pricing_method == OPTION_PRICING_MODEL.BLACK_SCHOLES.value: # Parameters for Black-Scholes model ticker = st.text_input('Ticker symbol', 'AAPL') - strike_price = st.number_input('Strike price', 300) + st.caption("Enter the stock symbol (e.g., AAPL for Apple Inc.)") + + # Fetch current price + current_price = get_current_price(ticker) + + if current_price is not None: + st.write(f"Current price of {ticker}: ${current_price:.2f}") + + # Set default and min/max values based on current price + default_strike = round(current_price, 2) + min_strike = max(0.1, round(current_price * 0.5, 2)) + max_strike = round(current_price * 2, 2) + + strike_price = st.number_input('Strike price', + min_value=min_strike, + max_value=max_strike, + value=default_strike, + step=0.01) + st.caption(f"The price at which the option can be exercised. Range: ${min_strike:.2f} to ${max_strike:.2f}") + else: + strike_price = st.number_input('Strike price', min_value=0.01, value=100.0, step=0.01) + st.caption("The price at which the option can be exercised. Enter a valid ticker to see a suggested range.") + risk_free_rate = st.slider('Risk-free rate (%)', 0, 100, 10) - sigma = st.slider('Sigma (%)', 0, 100, 20) + st.caption("The theoretical rate of return of an investment with zero risk. Usually based on government bonds. 0% means no risk-free return, 100% means doubling your money risk-free (unrealistic).") + + sigma = st.slider('Sigma (Volatility) (%)', 0, 100, 20) + st.caption("A measure of the stock's price variability. Higher values indicate more volatile stocks. 0% means no volatility (unrealistic), 100% means extremely volatile.") + exercise_date = st.date_input('Exercise date', min_value=datetime.today() + timedelta(days=1), value=datetime.today() + timedelta(days=365)) + st.caption("The date when the option can be exercised") if st.button(f'Calculate option price for {ticker}'): - # Getting data for selected ticker - data = get_historical_data(ticker) - st.write(data.tail()) - Ticker.plot_data(data, ticker, 'Adj Close') - st.pyplot() - - # Formating selected model parameters - spot_price = Ticker.get_last_price(data, 'Adj Close') - risk_free_rate = risk_free_rate / 100 - sigma = sigma / 100 - days_to_maturity = (exercise_date - datetime.now().date()).days - - # Calculating option price - BSM = BlackScholesModel(spot_price, strike_price, days_to_maturity, risk_free_rate, sigma) - call_option_price = BSM.calculate_option_price('Call Option') - put_option_price = BSM.calculate_option_price('Put Option') - - # Displaying call/put option price - st.subheader(f'Call option price: {call_option_price}') - st.subheader(f'Put option price: {put_option_price}') + try: + with st.spinner('Fetching data...'): + data = get_historical_data(ticker) + + if data is not None and not data.empty: + st.write("Data fetched successfully:") + st.write(data.tail()) + + fig = Ticker.plot_data(data, ticker, 'Close') + st.pyplot(fig) + + spot_price = Ticker.get_last_price(data, 'Close') + risk_free_rate = risk_free_rate / 100 + sigma = sigma / 100 + days_to_maturity = (exercise_date - datetime.now().date()).days + + BSM = BlackScholesModel(spot_price, strike_price, days_to_maturity, risk_free_rate, sigma) + call_option_price = BSM.calculate_option_price('Call Option') + put_option_price = BSM.calculate_option_price('Put Option') + + st.subheader(f'Call option price: {call_option_price:.2f}') + st.subheader(f'Put option price: {put_option_price:.2f}') + else: + st.error("Unable to proceed with calculations due to data fetching error.") + except Exception as e: + st.error(f"Error during calculation: {str(e)}") + else: + st.info("Click 'Calculate option price' to see results.") elif pricing_method == OPTION_PRICING_MODEL.MONTE_CARLO.value: # Parameters for Monte Carlo simulation ticker = st.text_input('Ticker symbol', 'AAPL') - strike_price = st.number_input('Strike price', 300) + st.caption("Enter the stock symbol (e.g., AAPL for Apple Inc.)") + + # Fetch current price + current_price = get_current_price(ticker) + + if current_price is not None: + st.write(f"Current price of {ticker}: ${current_price:.2f}") + + # Set default and min/max values based on current price + default_strike = round(current_price, 2) + min_strike = max(0.1, round(current_price * 0.5, 2)) + max_strike = round(current_price * 2, 2) + + strike_price = st.number_input('Strike price', + min_value=min_strike, + max_value=max_strike, + value=default_strike, + step=0.01) + st.caption(f"The price at which the option can be exercised. Range: ${min_strike:.2f} to ${max_strike:.2f}") + else: + strike_price = st.number_input('Strike price', min_value=0.01, value=100.0, step=0.01) + st.caption("The price at which the option can be exercised. Enter a valid ticker to see a suggested range.") + risk_free_rate = st.slider('Risk-free rate (%)', 0, 100, 10) - sigma = st.slider('Sigma (%)', 0, 100, 20) + st.caption("The theoretical rate of return of an investment with zero risk. Usually based on government bonds. 0% means no risk-free return, 100% means doubling your money risk-free (unrealistic).") + + sigma = st.slider('Sigma (Volatility) (%)', 0, 100, 20) + st.caption("A measure of the stock's price variability. Higher values indicate more volatile stocks. 0% means no volatility (unrealistic), 100% means extremely volatile.") + exercise_date = st.date_input('Exercise date', min_value=datetime.today() + timedelta(days=1), value=datetime.today() + timedelta(days=365)) + st.caption("The date when the option can be exercised") + number_of_simulations = st.slider('Number of simulations', 100, 100000, 10000) + st.caption("The number of price paths to simulate. More simulations increase accuracy but take longer to compute.") + num_of_movements = st.slider('Number of price movement simulations to be visualized ', 0, int(number_of_simulations/10), 100) + st.caption("The number of simulated price paths to display on the graph") if st.button(f'Calculate option price for {ticker}'): - # Getting data for selected ticker - data = get_historical_data(ticker) - st.write(data.tail()) - Ticker.plot_data(data, ticker, 'Adj Close') - st.pyplot() - - # Formating simulation parameters - spot_price = Ticker.get_last_price(data, 'Adj Close') - risk_free_rate = risk_free_rate / 100 - sigma = sigma / 100 - days_to_maturity = (exercise_date - datetime.now().date()).days - - # ESimulating stock movements - MC = MonteCarloPricing(spot_price, strike_price, days_to_maturity, risk_free_rate, sigma, number_of_simulations) - MC.simulate_prices() - - # Visualizing Monte Carlo Simulation - MC.plot_simulation_results(num_of_movements) - st.pyplot() - - # Calculating call/put option price - call_option_price = MC.calculate_option_price('Call Option') - put_option_price = MC.calculate_option_price('Put Option') - - # Displaying call/put option price - st.subheader(f'Call option price: {call_option_price}') - st.subheader(f'Put option price: {put_option_price}') + try: + with st.spinner('Fetching data...'): + data = get_historical_data(ticker) + + if data is not None and not data.empty: + st.write("Data fetched successfully:") + st.write(data.tail()) + + fig = Ticker.plot_data(data, ticker, 'Close') + st.pyplot(fig) + + spot_price = Ticker.get_last_price(data, 'Close') + risk_free_rate = risk_free_rate / 100 + sigma = sigma / 100 + days_to_maturity = (exercise_date - datetime.now().date()).days + + MC = MonteCarloPricing(spot_price, strike_price, days_to_maturity, risk_free_rate, sigma, number_of_simulations) + MC.simulate_prices() + + MC.plot_simulation_results(num_of_movements) + st.pyplot() + + call_option_price = MC.calculate_option_price('Call Option') + put_option_price = MC.calculate_option_price('Put Option') + + st.subheader(f'Call option price: {call_option_price:.2f}') + st.subheader(f'Put option price: {put_option_price:.2f}') + else: + st.error("Unable to proceed with calculations due to data fetching error.") + except Exception as e: + st.error(f"Error during calculation: {str(e)}") + else: + st.info("Click 'Calculate option price' to see results.") elif pricing_method == OPTION_PRICING_MODEL.BINOMIAL.value: # Parameters for Binomial-Tree model ticker = st.text_input('Ticker symbol', 'AAPL') - strike_price = st.number_input('Strike price', 300) + st.caption("Enter the stock symbol (e.g., AAPL for Apple Inc.)") + + # Fetch current price + current_price = get_current_price(ticker) + + if current_price is not None: + st.write(f"Current price of {ticker}: ${current_price:.2f}") + + # Set default and min/max values based on current price + default_strike = round(current_price, 2) + min_strike = max(0.1, round(current_price * 0.5, 2)) + max_strike = round(current_price * 2, 2) + + strike_price = st.number_input('Strike price', + min_value=min_strike, + max_value=max_strike, + value=default_strike, + step=0.01) + st.caption(f"The price at which the option can be exercised. Range: ${min_strike:.2f} to ${max_strike:.2f}") + else: + strike_price = st.number_input('Strike price', min_value=0.01, value=100.0, step=0.01) + st.caption("The price at which the option can be exercised. Enter a valid ticker to see a suggested range.") + risk_free_rate = st.slider('Risk-free rate (%)', 0, 100, 10) - sigma = st.slider('Sigma (%)', 0, 100, 20) + st.caption("The theoretical rate of return of an investment with zero risk. Usually based on government bonds. 0% means no risk-free return, 100% means doubling your money risk-free (unrealistic).") + + sigma = st.slider('Sigma (Volatility) (%)', 0, 100, 20) + st.caption("A measure of the stock's price variability. Higher values indicate more volatile stocks. 0% means no volatility (unrealistic), 100% means extremely volatile.") + exercise_date = st.date_input('Exercise date', min_value=datetime.today() + timedelta(days=1), value=datetime.today() + timedelta(days=365)) + st.caption("The date when the option can be exercised") + number_of_time_steps = st.slider('Number of time steps', 5000, 100000, 15000) + st.caption("The number of periods in the binomial tree. More steps increase accuracy but take longer to compute.") if st.button(f'Calculate option price for {ticker}'): - # Getting data for selected ticker - data = get_historical_data(ticker) - st.write(data.tail()) - Ticker.plot_data(data, ticker, 'Adj Close') - st.pyplot() - - # Formating simulation parameters - spot_price = Ticker.get_last_price(data, 'Adj Close') - risk_free_rate = risk_free_rate / 100 - sigma = sigma / 100 - days_to_maturity = (exercise_date - datetime.now().date()).days - - # Calculating option price - BOPM = BinomialTreeModel(spot_price, strike_price, days_to_maturity, risk_free_rate, sigma, number_of_time_steps) - call_option_price = BOPM.calculate_option_price('Call Option') - put_option_price = BOPM.calculate_option_price('Put Option') - - # Displaying call/put option price - st.subheader(f'Call option price: {call_option_price}') - st.subheader(f'Put option price: {put_option_price}') \ No newline at end of file + try: + with st.spinner('Fetching data...'): + data = get_historical_data(ticker) + + if data is not None and not data.empty: + st.write("Data fetched successfully:") + st.write(data.tail()) + + fig = Ticker.plot_data(data, ticker, 'Close') + st.pyplot(fig) + + spot_price = Ticker.get_last_price(data, 'Close') + risk_free_rate = risk_free_rate / 100 + sigma = sigma / 100 + days_to_maturity = (exercise_date - datetime.now().date()).days + + BOPM = BinomialTreeModel(spot_price, strike_price, days_to_maturity, risk_free_rate, sigma, number_of_time_steps) + call_option_price = BOPM.calculate_option_price('Call Option') + put_option_price = BOPM.calculate_option_price('Put Option') + + st.subheader(f'Call option price: {call_option_price:.2f}') + st.subheader(f'Put option price: {put_option_price:.2f}') + else: + st.error("Unable to proceed with calculations due to data fetching error.") + except Exception as e: + st.error(f"Error during calculation: {str(e)}") + else: + st.info("Click 'Calculate option price' to see results.") \ No newline at end of file