Python for Finance Part 4: Stock Options
Me and my portfolio

Python for Finance Part 4: Stock Options

Today we kick it into fifth gear. We are injecting rocket fuel into our trading strategies.

Options are financial contracts that allow the holder to buy or sell an underlying asset, like GameStop (GME) stock, at a predetermined price before a specified expiration date.

For instance, suppose you hold an option to buy 100 shares of GME at $200 per share before a certain date. If the stock price rises to $300 before the option expires, you could choose to exercise the option and purchase the shares at the lower price of $200, making a profit of $10,000 ($100 per share x 100 shares).

Alternatively, if the stock price falls below $200, you could let the option expire and only lose the cost of buying the option.

Options trading can be risky, and investors can lose a lot of money if they are not careful. Here are some reasons why:

  1. Options can be very powerful: Options allow you to control a lot of stock with a small amount of money. While this can be good when things go right, it can also be very bad when things go wrong.
  2. Options have an expiration date: If the price of the stock does not move in the direction you were expecting before the option expires, then the option can become worthless, and you can lose all the money you paid for it.
  3. Options can be complicated: Options have many different variables that can affect their value, such as the price of the stock, the time remaining until expiration, and the volatility of the stock. It can be difficult to understand all of these variables and how they interact with each other.

In short, options can be a useful tool for managing risk or speculating on price movements, but they can also be very risky if you do not fully understand how they work. It is important to educate yourself and seek professional advice before engaging in options trading.

Today we are going to use the Black-Scholes model to suggest options to buy.

No alt text provided for this image
The Black-Scholes model

The Black-Scholes model is a mathematical formula used to estimate the value of stock options.

There are many variables to take into consideration, and a lot of data can be pulled from yfinance.

  1. Stock Price (S): The current market price of the underlying stock.
  2. Strike Price (K): The predetermined price at which the option can be exercised.
  3. Time to expiration (t): The time remaining until the option expires.
  4. Risk-free interest rate (r): The interest rate used to discount future cash flows to their present value. It is assumed that the risk-free rate remains constant over the life of the option.
  5. Volatility (σ): The measure of the stock's standard deviation of returns over a period of time. It is a measure of how much the stock price is likely to fluctuate in the future.

Using this information, the model estimates the fair price of an option at a given point in time.

The model assumes that the price of the underlying stock follows a random walk, meaning that its movement is unpredictable and based on many different factors. It also assumes that the stock price follows a log-normal distribution, meaning that the stock price moves by a certain percentage each day, rather than by a fixed amount.

Lets create a new python file options.py and write some code.

import yfinance as yf
import numpy as np
from scipy.stats import norm
import pandas as pd
import matplotlib.pyplot as plt

# Set stock ticker and expiry date
ticker = input("Enter stock ticker: ")
expiry = input("Enter expiry date: ")

# Get stock data for the past year
start_date = pd.to_datetime('today') - pd.DateOffset(years=1)
end_date = pd.to_datetime('today')
stock_data = yf.download(ticker, start=start_date, end=end_date)

# Calculate historical volatility
returns = stock_data['Adj Close'].pct_change().dropna()
volatility = returns.std() * np.sqrt(252)

# Get option chain for stock ticker and expiry date
option_chain = yf.Ticker(ticker).option_chain(expiry)

# Filter option chain for puts and calls
puts = option_chain.puts.sort_values(by='lastPrice')
calls = option_chain.calls.sort_values(by='lastPrice')

# Filter for conservative trader
conservative_puts = puts[puts["inTheMoney"] == True].head(5)
conservative_calls = calls[calls["inTheMoney"] == True].tail(5)

# Set risk-free interest rate
r = -0.0626

# Calculate time to expiration in years
t = (pd.to_datetime(expiry) - pd.to_datetime('today')).days / 365        

The above code imports our favorite yfinance library as well as numpy and pandas for some data handling. We need scipy.stats for some statistics and of course matplotlib for plotting.

We load the options chain from yahoo finance regarding the specific stock. We also calculate the historical volatility which will help us approximate some options better using data from the previous year.

And then we add the Black-Scholes model code. The most important aspect.


# Define Black-Scholes formula
def black_scholes(S, K, t, r, sigma, option='call'):
    d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * t) / (sigma * np.sqrt(t))
    d2 = d1 - sigma * np.sqrt(t)
    if option == 'call':
        return S * norm.cdf(d1) - K * np.exp(-r * t) * norm.cdf(d2)
    else:
        return K * np.exp(-r * t) * norm.cdf(-d2) - S * norm.cdf(-d1)        

As you can see, the function makes use of every variable, just the way it's represented in the equation. I even use exponentiation and logarithmic functions.

The equation takes into account various factors, such as the current stock price, the option's strike price, the time until expiration, the risk-free interest rate, and the stock's volatility. By plugging in these variables, the equation calculates the theoretical price of the option, which represents the fair value of the option in a perfectly efficient market.

Now it's time to add the remaining code which filters "conservative options". Also known as "in-the-money" options. I felt this was a decent way to assign some sort of financial safety.


# Estimate fair price for each option using Black-Scholes formula
conservative_puts['fair_price'] = black_scholes(
    conservative_puts['lastPrice'], conservative_puts['strike'], t, r, volatility,
    option='put')
conservative_calls['fair_price'] = black_scholes(
    conservative_calls['lastPrice'], conservative_calls['strike'], t, r, volatility,
    option='call')

# Calculate expected return for each option
conservative_puts['expected_return'] = (conservative_puts['fair_price'] - conservative_puts['lastPrice']) / \
                                       conservative_puts['lastPrice']
conservative_calls['expected_return'] = (conservative_calls['fair_price'] - conservative_calls['lastPrice']) / \
                                        conservative_calls['lastPrice']

# Rank options by expected return and suggest top 3 put and call
suggested_puts = conservative_puts.sort_values('expected_return', ascending=False).head(3)
suggested_calls = conservative_calls.sort_values('expected_return', ascending=False).head(3)

# Print suggested options and stock price
market_price = yf.Ticker(ticker).fast_info['lastPrice']
print()
print("Suggested puts:")
print(suggested_puts[['strike', 'lastTradeDate', 'lastPrice', 'ask', 'impliedVolatility', 'fair_price',
                      'expected_return']].to_string(index=False))
print("\nStock Price:")
print(f"The current price of {ticker} is ${market_price:.2f}")

print("\nSuggested calls:")
print(suggested_calls[['strike', 'lastTradeDate', 'lastPrice', 'ask', 'impliedVolatility', 'fair_price',
                       'expected_return']].to_string(index=False))
        

The remaining code ranks the options by their expected return and returns them. I also added the stock price in the center of the output.

Lets test the code at this point. It's time to see some GME options! I found some option contracts that expire on 2023-03-31.

/home/bot/PycharmProjects/finance/venv/bin/python /home/bot/PycharmProjects/finance/options.py
Enter stock ticker: GME
Enter expiry date: 2023-03-31
[*********************100%***********************]? 1 of 1 completed

Suggested puts:
?strike???????????? lastTradeDate? lastPrice? ask? impliedVolatility? fair_price? expected_return
?? 24.0 2023-03-24 19:59:43+00:00?????? 1.49 1.50?????????? 1.106450?? 22.534710??????? 14.123966
?? 25.0 2023-03-24 19:59:56+00:00?????? 2.22 2.22?????????? 1.167973?? 22.805739???????? 9.272856
?? 26.0 2023-03-24 19:02:28+00:00?????? 3.15 3.10?????????? 1.288089?? 22.876769???????? 6.262466

Stock Price:
The current price of GME is $23.98

Suggested calls:
?strike???????????? lastTradeDate? lastPrice?? ask? impliedVolatility? fair_price? expected_return
??? 5.0 2023-03-15 17:42:26+00:00?????? 11.3 19.30?????????? 7.640625??? 6.294852??????? -0.442933
?? 10.0 2023-03-24 14:51:47+00:00?????? 12.3 14.25?????????? 4.257817??? 2.318505??????? -0.811504
?? 13.5 2023-03-22 17:36:05+00:00?????? 11.4 10.75?????????? 2.972659??? 0.062018??????? -0.994560

Process finished with exit code 0        

Very cool, as you can see there are numerous call and put options and a calculated fair price and expected return. We can also pull information from yfinance about their implied volatility. I feel like this console output somewhat mimics the option trading page of my brokerage account.

Lets finish up and add code for plotting the options against the stock price over time.

# Plot stock price and options

fig, ax = plt.subplots()

# Plot stock price
stock_data['Adj Close'].plot(ax=ax, label=ticker)

# Plot put options
for i, row in suggested_puts.iterrows():
    ax.axhline(y=row['strike'], color='r', linestyle='-')

# Plot call options
for i, row in suggested_calls.iterrows():
    ax.axhline(y=row['strike'], color='g', linestyle='-')

plt.legend()
plt.title(f'{ticker} Options for {expiry}')
plt.xlabel('Date')
plt.ylabel('Price')

plt.savefig("plot.png")        

Now we can execute the code and see suggested options as well as save a new image. This time I'm looking at Advanced Micro Devices, Inc. (AMD).

/home/bot/PycharmProjects/finance/venv/bin/python /home/bot/PycharmProjects/finance/options.py
Enter stock ticker: AMD
Enter expiry date: 2023-03-31
[*********************100%***********************]? 1 of 1 completed

Suggested puts:
?strike???????????? lastTradeDate? lastPrice? ask? impliedVolatility? fair_price? expected_return
?? 98.0 2023-03-24 19:59:46+00:00?????? 2.75 2.78?????????? 0.500737?? 95.350898??????? 33.673054
? 100.0 2023-03-24 19:58:55+00:00?????? 3.80 3.85?????????? 0.494146?? 96.302957??????? 24.342883
? 105.0 2023-03-24 19:22:38+00:00?????? 7.32 7.70?????????? 0.520024?? 97.788105??????? 12.359031

Stock Price:
The current price of AMD is $97.95

Suggested calls:
?strike???????????? lastTradeDate? lastPrice?? ask? impliedVolatility? fair_price? expected_return
?? 45.0 2023-03-23 19:31:25+00:00????? 54.45 53.60?????????? 2.414066??? 9.410405??????? -0.827173
?? 40.0 2023-03-10 20:33:22+00:00????? 43.08 58.60?????????? 2.757816??? 3.309535??????? -0.923177
?? 50.0 2023-03-24 14:01:54+00:00????? 48.90 48.65?????????? 2.195317??? 0.972903??????? -0.980104

Process finished with exit code 0        

If we open the image plot.png in the same directory we have a beautiful image of what an option contact implies.

No alt text provided for this image
AMD stock and suggested conservative options

Notice how we have plotted a nice picture of the suggested calls and puts for technical analysis. AMD has recently had a price increase which effects the placement of puts and calls.

Now think realistically about this program. I chose AMD because it's a stable and profitable technology company. Lets instead look at First Republic (FRC)

/home/bot/PycharmProjects/finance/venv/bin/python /home/bot/PycharmProjects/finance/options.py
Enter stock ticker: FRC
Enter expiry date: 2023-03-31
[*********************100%***********************]? 1 of 1 completed

Suggested puts:
?strike???????????? lastTradeDate? lastPrice? ask? impliedVolatility? fair_price? expected_return
?? 13.0 2023-03-24 19:57:12+00:00?????? 3.30? 3.4?????????? 4.226567??? 9.713384???????? 1.943450
?? 14.0 2023-03-24 19:59:04+00:00?????? 3.90? 4.0?????????? 4.195317?? 10.114414???????? 1.593439
?? 15.0 2023-03-24 19:58:30+00:00?????? 4.44? 4.7?????????? 4.101567?? 10.575444???????? 1.381857

Stock Price:
The current price of FRC is $12.36

Suggested calls:
?strike???????????? lastTradeDate? lastPrice? ask? impliedVolatility?? fair_price? expected_return
??? 5.0 2023-03-24 19:36:51+00:00?????? 7.45 7.90?????????? 5.562503 2.445332e+00??????? -0.671767
?? 10.0 2023-03-24 19:59:33+00:00?????? 4.00 4.20?????????? 4.562504 1.809085e-12??????? -1.000000
?? 12.0 2023-03-24 19:59:42+00:00?????? 2.90 2.95?????????? 4.050786 2.539307e-26??????? -1.000000

Process finished with exit code 0        
No alt text provided for this image
First Republic Bank

First Republic Bank is not doing so well, that's because the stock has lost 90% of it's value as of late. Look at the fair price calculated for that $12 call! It's an absolutely minuscule number!

It's not a good idea to try to "catch the falling knife" here with a call option. Or maybe not even a good idea to place puts. The stock could just go to zero right? Or maybe they'll get another $30 billion and rocket up to $40 per share in pre-market trading. A lot of things can happen and no one really knows anything, such is the nature of the free market.

Here is the entire finalized code, please use it carefully.

import yfinance as yf
import numpy as np
from scipy.stats import norm
import pandas as pd
import matplotlib.pyplot as plt

# Set stock ticker and expiry date
ticker = input("Enter stock ticker: ")
expiry = input("Enter expiry date: ")

# Get stock data for the past year
start_date = pd.to_datetime('today') - pd.DateOffset(years=1)
end_date = pd.to_datetime('today')
stock_data = yf.download(ticker, start=start_date, end=end_date)

# Calculate historical volatility
returns = stock_data['Adj Close'].pct_change().dropna()
volatility = returns.std() * np.sqrt(252)

# Get option chain for stock ticker and expiry date
option_chain = yf.Ticker(ticker).option_chain(expiry)

# Filter option chain for puts and calls
puts = option_chain.puts.sort_values(by='lastPrice')
calls = option_chain.calls.sort_values(by='lastPrice')

# Filter for conservative trader
conservative_puts = puts[puts["inTheMoney"] == True].head(5)
conservative_calls = calls[calls["inTheMoney"] == True].tail(5)

# Set risk-free interest rate
r = -0.0626

# Calculate time to expiration in years
# Define Black-Scholes formula
def black_scholes(S, K, t, r, sigma, option='call'):
    d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * t) / (sigma * np.sqrt(t))
    d2 = d1 - sigma * np.sqrt(t)
    if option == 'call':
        return S * norm.cdf(d1) - K * np.exp(-r * t) * norm.cdf(d2)
    else:
        return K * np.exp(-r * t) * norm.cdf(-d2) - S * norm.cdf(-d1)


# Estimate fair price for each option using Black-Scholes formula
conservative_puts['fair_price'] = black_scholes(
    conservative_puts['lastPrice'], conservative_puts['strike'],
    (pd.to_datetime(expiry) - pd.to_datetime('today')).days / 365, r, volatility,
    option='put')
conservative_calls['fair_price'] = black_scholes(
    conservative_calls['lastPrice'], conservative_calls['strike'],
    (pd.to_datetime(expiry) - pd.to_datetime('today')).days / 365, r, volatility,
    option='call')

# Calculate expected return for each option
conservative_puts['expected_return'] = (conservative_puts['fair_price'] - conservative_puts['lastPrice']) / \
                                       conservative_puts['lastPrice']
conservative_calls['expected_return'] = (conservative_calls['fair_price'] - conservative_calls['lastPrice']) / \
                                        conservative_calls['lastPrice']

# Rank options by expected return and suggest top 3 put and call
suggested_puts = conservative_puts.sort_values('expected_return', ascending=False).head(3)
suggested_calls = conservative_calls.sort_values('expected_return', ascending=False).head(3)

# Print suggested options and stock price
market_price = yf.Ticker(ticker).fast_info['lastPrice']
print()
print("Suggested puts:")
print(suggested_puts[['strike', 'lastTradeDate', 'lastPrice', 'ask', 'impliedVolatility', 'fair_price',
                      'expected_return']].to_string(index=False))
print("\nStock Price:")
print(f"The current price of {ticker} is ${market_price:.2f}")

print("\nSuggested calls:")
print(suggested_calls[['strike', 'lastTradeDate', 'lastPrice', 'ask', 'impliedVolatility', 'fair_price',
                       'expected_return']].to_string(index=False))

# Plot stock price and options

fig, ax = plt.subplots()

# Plot stock price
stock_data['Adj Close'].plot(ax=ax, label=ticker)

# Plot put options
for i, row in suggested_puts.iterrows():
    ax.axhline(y=row['strike'], color='r', linestyle='-')

# Plot call options
for i, row in suggested_calls.iterrows():
    ax.axhline(y=row['strike'], color='g', linestyle='-')

plt.legend()
plt.title(f'{ticker} Options for {expiry}')
plt.xlabel('Date')
plt.ylabel('Price')

plt.savefig("plot.png")        

Options are very dangerous and I encourage you to make intelligent investing decisions always. - Henry

Excelente!!!!!! tu trabajo. Luego de hacer los ajustes sugeridos por Pete Granneman, el codigo funciona muy bien. FELICITACIONES Y Saludos

回复
Henry Meier

Software Engineer

1 年

Thank you for this correction!

回复
Pete G.

Outside Sales Representative

1 年

Great concept! However, the code implemented uses the spot price of the option in place of the Spot Equity Price (of the Equity) in the BSM equation leading to erroneous 'fair_price' . Code should use 'market_price' in place of conservative_puts['lastPrice'] and conservative_calls['lastPrice'] for the "S" value.

要查看或添加评论,请登录

Henry Meier的更多文章

  • Chess Part 2: Finish The Game

    Chess Part 2: Finish The Game

    Welcome back, today we are working on the Front-End. OK, on to the big boy file.

  • Chess Part 1: Multiplayer

    Chess Part 1: Multiplayer

    It's been quite some time since I was able to write an article. Many of my articles have started to blow up so I…

  • Neural Network in C Part 4: Hidden Layer Analysis

    Neural Network in C Part 4: Hidden Layer Analysis

    I want to look under the hood today. Obviously we can see the input layer and output layer.

  • Neural Network in C Part 3: Assembly VS C

    Neural Network in C Part 3: Assembly VS C

    Our Neural Network is fast but we can make it even faster. We will convert the code to assembly, then race the two side…

    2 条评论
  • Neural Network in C Part 2: C Programming

    Neural Network in C Part 2: C Programming

    In this article, we explore how to build a simple feed forward neural network in C to recognize handwritten digits from…

  • Neural Network in C Part 1: Idea

    Neural Network in C Part 1: Idea

    Welcome, normally I enjoy programming in Python and using the TensorFlow library to create machine learning…

  • Free CRM Part 2: Client Notes, Dark Mode, Adding Customers

    Free CRM Part 2: Client Notes, Dark Mode, Adding Customers

    We have a lot of stuff to add to our free CRM software. Small businesses are counting on us! Small businesses don't…

  • Free CRM Part 1: Idea

    Free CRM Part 1: Idea

    Salesforce is expensive. Lets make our own CRM (Customer Relationship Management) software.

  • Firmware Engineering Part 3: OLED Display

    Firmware Engineering Part 3: OLED Display

    The temperature/humidity sensor works. Currently, this device communicates with a website and shares data over MQTT.

  • Firmware Engineering Part 2: Data Logging Website

    Firmware Engineering Part 2: Data Logging Website

    Last article, we wrote a simple MicroPython program for the ESP32. This device broadcasts raw temperature and humidity…

社区洞察

其他会员也浏览了