Max Pain Analysis

One of the biggest things to come out of the GME pop of January 2021 was widespread discussion about the effect institutional trading, particulary hedge finds, have on the market.

What appeared to be battles during Friday power hours led many to look into the Max Pain Theory, which is a pretty interesting concept.

Background

According to the Max Pain Theory, underlying prices will tend to gravitate toward a point of maximum pain - the point at which the most options will expire worthless.

Obviously there are a couple assumptions here. I'm going to focus on a fun potential cause where the market is manipulated to hit the "max pain" point. Everyone loves a good conspiracy theory.

Here's a quick list of assumptions:

  • The goal each expiration cycle is to cause the most pain to option buyers
  • Options are primarily sold by Market Makers (MM), and typically bought by retail
  • Market Makers have the ability to shift prices
  • By hitting this price, Market Makers will collect the most "free" premium and pay out the least amount of money

So when the evil Hedge Fund Cabal™ sells a bunch of options, they'll do whatever it takes to make sure these expire worthless. Note - the number of options that expire worthless or are traded to close is why I primarily sell options. I love a flat market, theta is my friend.

Options volume is constantly shifting based on underlying movement, but the Max Pain point is a relatively straightforward calculation. In this post we're going to use Yahoo Finance data to calculate the Max Pain price for an equity. Keep in mind that this is just a loose "theory" (if that), the value is constantly changing, and that it's believed to work better the closer to expiration you are.

Calulating Max Pain

I've seen a few guides that overcomplicate the actual process of getting the max pain price for an options chain, and yet still gloss over some of the foundational details.

Let's create a fake XYZ ticker with an expiration date of 2021-03-05. The option strikes and details are in the following table:

Calls

Strike Last Price Volume Open Interest Implied Volatility
125 3.21 4325 45 32.54
130 2.11 2191 21 34.23
135 0.71 700 7 78.92

Puts

Strike Last Price Volume Open Interest Implied Volatility
125 0.65 54 2 89.43
130 1.83 324 43 73.32
135 3.64 928 78 17.53

(Please note that these values have no bearing on each other, I just typed some up and we'll see if they make sense)

The only fields you need to calculate the Max Pain point are the Strike, and then the call and put open interest. Open Interest represents how many contracts are outstanding, and haven't been exercised or closed out.

In a psuedo-code statement, the first thing to do is concatenate the calls and puts tables on the strike value (the full python code will be placed at the end of this post)

So you end up with:

Strike Call Open Interest Put Open Interest
125 45 54
130 21 324
135 7 928

Once you have this, you can calculate the "gain" that will be collected by options holders, or rather, the loss that will be realized by MMs if the underlying is at a certain price at expiration. Let's start with the 125 strike. If the underlying finishes at exactly 125, all calls listed are worthless. Assume that at-the-money (ATM) contracts are worthless as well, so the 125 put is also a zero. This leaves the rest of the puts in the money.

Multiply the open interest of these puts by the difference between the strike and 125, using the following formula:

Formula for each strike price, sum for each set:

Call Loss = [(Underlying - Strike Price) * Open Interest]

Put Loss = [(Strike Price - Underlying) * Open Interest]]

In the example of the underlying finishing at 125 at expiration:

Total Loss = Call Loss + Put Loss

Call Loss = 0

Put Loss = [(125 - 125) * 54] + [(130 - 125) * 324] + [(135 - 125) * 928] = 0 + 1,620 + 9,280 = 10,900

Repeat this for the other strikes:

130:

Call Loss: 255

Put Loss: 4,640

Total Loss: 4,895

135:

Call Loss: 555

Put Loss: 0

Total Loss: 555

Once you identify the minimum loss point when combining puts and calls, you have the Max Pain point. In this example, if the underlying moves to 135 the options writers will lose the least amount of money.

Obviously this is a very simplified example, as there are usually many strike prices that you can write or buy options to.

Yahoo Finance and Python

If you're using Yahoo Finance and Python, when you look up an options chain you'll receive a pandas DataFrame in a similar format, with a few columns that you don't need. The following code snippet will calculate max pain and return a row containing the strike, open interest, and various loss sums that MMs would experience:

def oi_to_dollars(underlying_price, chain, option_type):
    # Use either function depending on if call or put
    if option_type == "call":
        # Calulate distance from strike
        chain["distance"] = underlying_price - chain["strike"]
        # Multiply by Open Interest
        chain["loss"] = chain["distance"] * chain["callOI"]
        # Filter out ATM and OTM calls
        chain = chain[chain["strike"] <= strike]
        # Return sum of loss
        return chain["loss"].sum()
    if option_type == "put":
        # Same as above, but opposite
        chain["distance"] = chain["strike"] - underlying_price
        chain["loss"] = chain["distance"] * chain["callOI"]
        chain = chain[chain["strike"] >= strike]
        return chain["loss"].sum()


def calc_pain(symbol, exp):

    # The only columns we care about
    cols = ["strike", "openInterest"]

    # Split the chains into calls and puts
    options_chain = yf.Ticker(symbol).option_chain(exp)
    # Rename open interest columns so we know which are calls and which are puts
    call_chain = options_chain.calls[cols].rename(
        columns={"openInterest": "callOI"})
    put_chain = options_chain.puts[cols].rename(
        columns={"openInterest": "putOI"})

    # Join the calls and puts chain on the strike column
    chain_pain = call_chain.set_index(
        "strike").join(put_chain.set_index("strike")).reset_index()

    # Use the oi_to_dollars function above to calculate call and put loss per strike
    chain_pain["call_loss"] = chain_pain["strike"].apply(
        oi_to_dollars, args=[chain_pain, "call"])
    chain_pain["put_loss"] = chain_pain["strike"].apply(
        oi_to_dollars, args=[chain_pain, "put"])

    # Calculate total loss for calls and puts
    chain_pain["total_loss"] = chain_pain["call_loss"] + chain_pain["put_loss"]

    # Return the row with the least total loss - what the Hedge Fund Cabal™ wants
    max_pain = chain_pain[chain_pain["total_loss"]
                            == chain_pain["total_loss"].min()]

    return max_pain

Conclusion

Max Pain Theory is controversial, but definitely interesting. I put it in the same boat as most Technical Analysis - a tool to be used but not gospel (as nothing is).

I highly recommend looking up some more detail on this if you think it's something that might help you out in your investing journey.

Personally (and I reiterate that I am not a financial advisor, just an engineer that likes dashboards) I use it as a quick check if I'm bullish or bearish on a stock, and that ties into my entries into positions. I plan on having a solid dashboard of max pain related metrics for liquid symbols on stock-spike.com so maybe there's some new indicator that I'll discover and break the market. We'll see.

Written on March 1, 2021