emre-ozer.github.io

Options with Polygon API


Github repository for this project: https://github.com/emre-ozer/mathematical-finance/blob/main/polygon-options/option.py

What is polygon.io?


polygon.io is provides APIs for various financial markets including stock, option, index, forex and crypto. For this project, we will need option market data (as well as data about the underlyings). I use the free version, which has feature and data limitations. I also use python 3.11 throughout.

Option snapshot is a paid feature of the polygon option API. Basically, it provides all information about an options contract: current price, time to expiry, break even price, implied volatility, Greeks and so on. The aim of this mini-project is to implement this from scratch.

For reference, here is the documentation I have referred to: https://polygon.readthedocs.io/en/latest/index.html

Roadmap


Here is a rough outline of the roadmap I followed for the implementation:

  1. Determine inputs: risk-free rate \(r\), dividend rate of underlying \(d\), spot price \(S\), time of expiry \(T\), strike \(K\).
  2. Compute the implied volatility.
  3. Compute Greeks.
  4. Clean up code and organise in an option class (OOP).

Before we get started, we need to initialize the python clients for three polygon APIs: options, stocks, and reference. This is done through:

import polygon
reference_client = polygon.ReferenceClient(client_ID)
stocks_client = polygon.StocksClient(client_ID)
options_client = polygon.OptionsClient(client_ID)

Determining inputs


Computing the implied volatility


This is simple, one just needs to fetch the market price of the option and solve for \(\sigma\) for which it matches the Black-Scholes price. The market price on the previous day is fetched by

options_client.get_previous_close(opt_ticker)

Again, I am using the volume weighted price, but can also consider the closing price. To solve for \(\sigma\), I use scipy.fsolve. To illustrate how this works, consider a call option. fsolve finds the value at which a function equals zero, and so we write a function that outputs the difference between the Black-Scholes price and the market price of an options contract:

def __BS_call(self, vol):
    S = self.spot
    K = self.strike
    r = self.r
    d = self.d
    T = self.T
    a1 = (log(S/K) + (r - d + 0.5*vol*vol)*T) / (vol*sqrt(T))
    a2 = (log(S/K) + (r - d - 0.5*vol*vol)*T) / (vol*sqrt(T))
    return exp(-d*T)*S*self.__phi(a1) - K*exp(-r*T)*self.__phi(a2) - self.market_price

Note that this is a function of an option class, all inputs to the Black-Scholes formula are attributes of an instance of this class - except for the volatility which we solve for.

As a side note, the function __phi(x) here is the cumulative normal function, and is easily implemented in terms of the error function:

def __phi(self, x):
    return (1.0 + erf(x / sqrt(2.0))) / 2.0

Greeks


Once we have solved for the implied volatility, we simply evaluate the analytic expressions for the Greeks. See my page on the subject for a list of the analytic expressions. To illustrate, the delta is computed by the funciton:

def __Delta(self):
    S = self.spot
    K = self.strike
    r = self.r
    d = self.d
    T = self.T
    vol = self.implied_vol
    a1 = (log(S/K) + (r - d + 0.5*vol*vol)*T) / (vol*sqrt(T))
    a2 = (log(S/K) + (r - d - 0.5*vol*vol)*T) / (vol*sqrt(T))
    if self.type == 'C':
        return exp(-d*T)*self.__phi(a1) + (exp(-d*T)*S*exp(-a1*a1/2) - exp(-r*T)*K*exp(-a2*a2/2)) / (S*vol*sqrt(2*pi*T))
    else:
        return exp(-d*T)*self.__phi(a1) + (exp(-d*T)*S*exp(-a1*a1/2) - exp(-r*T)*K*exp(-a2*a2/2)) / (S*vol*sqrt(2*pi*T)) - exp(-d*T)

where the type variable equals 'C' for a call, and 'P' for a put.

Output and a test run


I have structured the output similar to the polygon's snapshot function, it returns a set of dictionaries:

def snapshot(self):
    details = {"contract_type": 'call' if self.type == 'C' else 'put',
                "exercise_style": self.exercise_style,
                "expiration_date": self.expiry_date.strftime('%Y-%m-%d'),
                "strike_price": self.strike,
                "ticker": self.ticker}
    greeks = {"delta": self.__Delta(),
                "gamma": self.__Gamma(),
                "vega": self.__Vega(),
                "rho": self.__Rho(),
                "theta": self.__Theta()}
    return {"details": details, 
            "implied_volatility": self.implied_vol,
                "greeks" : greeks }

The exercise style of the option is accessed through:

self.exercise_style = reference_client.get_option_contract(self.ticker).get('results').get('exercise_style')

As an example case, consider a call option struck at 200 for AAPL, expiring on 2023-08-18. When we run the code

opt1 = Option('AAPL', 200, 'C', dt.date(2023,8,18))
print(opt1.snapshot())

we get the output (at the time of writing 2023-06-20)

{'details': {'contract_type': 'call', 
    'exercise_style': 'american', 
    'expiration_date': '2023-08-18', 
    'strike_price': 200, 
    'ticker': 'AAPL230818C00200000'}, 
'implied_volatility': 0.19197814859347587, 
'greeks': {'delta': 0.2025270778382304, 
    'gamma': 0.019533409530053225, 
    'vega': 21.20358805737834, 
    'rho': 5.9148925438389846, 
    'theta': -14.05094136288048}}