Language

Multilingual content from IBKR

Close Navigation
Learn more about IBKR accounts
Handling Options Chains

Handling Options Chains

Posted May 1, 2024 at 3:26 pm
Andrew Wise
Interactive Brokers

One of the most ubiquitous questions received for the Interactive Brokers API team is the process for users to receive options chain data through the WebAPI. And while we have received some positive feedback over our existing Options Chains Documentation, we felt there was room for additional guidance on how a user could implement this system. Throughout this article, I’ll present one method for retrieving an option chain programmatically.

As users may be aware already, the Client Portal API requires that users query the /iserver/secdef/search, /iserver/secdef/strikes, and /iserver/secdef/info sequentially, with no means around this process. However, the implementation here will focus on building out our contract libraries pre-emptively before moving into the daily trading. As a result, the expectation for this script would be to run it once in the morning at the start of a month rather than intra-day.

While I am using Python in this scenario, these steps can be translated to most other programming languages given the RESTful request structure. For the Python libraries used in this script, I’ll be referencing the external libraries documented in our Endpoints section, focusing primarily on “requests”, “urllib3”, and “csv” from the python standard library. Because I’m working in the client portal gateway, I will also be passing verify=False in my requests, though this may be skipped.

import requests
import urllib3
import csv
# Ignore insecure error messages
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

After establishing our imports, I’ll define our secdefSearch method to create our initial search request and handle the returned data. We can set the arguments for symbol and listingExchange, so we can filter the exact contract we’re looking for without having to sort the data using the primary listing exchange.

Moving into our method, I’ll define our “url” variable as f”https://localhost:5001/v1/api/iserver/secdef/search?symbol={symbol}”. With a URL set, we can make our request, search_request, set to ‘requests.get(url=url, verify=False)’. If you print search_request.json() directly, you’ll find an array of several contracts.

import def secdefSearch(symbol, listingExchange):
  url = f'https://localhost:5001/v1/api/iserver/secdef/search?symbol={symbol}'
  search_request = requests.get(url=url, verify=False)

We don’t necessarily care about all of these contracts, so now we can look to filter it using our listingExchange argument from before. To do this, I’ll loop through each contract in our json, then if any of the contract “description” values match our listingExchange, we’ll have found the appropriate contract. Here, I can set our underConid variable to the contract “conid” field.

  for contract in search_request.json():
    if contract["description"] == listingExchange:
      underConid = contract["conid"]

While we are still in the contract we’re looking for, we can pull out the expiration month’s we’re looking for. I’ll do this by iterating through the secType items in the contract “sections” section. Here, if I find a secType “secType” that is an “OPT”, we’ll have our options contract months. I will set our “months” variable to the secType “months” section, and split the value on the semicolons.  Now we can return our underConid and months variables.

      for secType in contract["sections"]:
        if secType["secType"] == "OPT":
          months = secType["months"].split(';')
  return underConid,months

With our first method settled, we can create our name-main idiom and make our initial call to the secdefSearch method from there. At the bottom of the file, we can build out the standard ‘if __name__ == “__main__”:’, then set matching variables underConid and months equal to the secdefSearch method. I will pass in “AAPL” and “NASDAQ” as my arguments, but you’re welcome to query whichever options contract you’d like.

I will also be creating a new variable in our idiom, month which I will set to the 0 value of our returned months method. You’re welcome to query whichever contracts you’d like; however, I typically prefer to focus on the front month, so using the first indexed item will always bring up the front month.

if __name__ == "__main__":
  underConid,months = secdefSearch("AAPL", "NASDAQ")
  month = months[0]

Before jumping into the strikes endpoint, I’d like to calculate the approximate price of the instrument to find what is in-the-money. To do this, I’ll create a snapshotData method, taking an argument for our underConid variable. Similar to our last method, I’ll create a ‘url’ variable and set it to “f’https://localhost:5001/v1/api/iserver/marketdata/snapshot?conids={underConid}&fields=31’”. This is the market data snapshot endpoint that can be used to retrieve the last price {31} for the instrument. I can then create two requests to the same url, using the format of request.get(url=url,verify=False) just like before. We’re sending the request twice due to the mandatory preflight request for the endpoint. I will set my second request to the snapshot variable, to pull from it later. To conclude the method, I’ll simply return snapshot.json()[0][“31”] to return the Last price received.

def snapshotData(underConid):
  url = f'https://localhost:5001/v1/api/iserver/marketdata/snapshot?conids={underConid}&fields=31'
  requests.get(url=url, verify=False)
  snapshot = requests.get(url=url, verify=False)
  return snapshot.json()[0]["31"]

We can move on and define the secdefStrikes method, passing our underConid and month variables. Within our method, I’ll create the variable, snapshot, and set it to the float of our snapshotData’s response. We’ll be using the value for a calculation later, so we can’t use it as the String value we retrieved it as.  I will also create a list named itmStrikes, which we’ll leave empty but refer to later. And as usual, I’ll create a url variable set to the /iserver/secdef/strikes endpoint. And while I’ll be passing the underConid and month variables accordingly, I will set the secType param to “OPT”, as I have no interest in retrieving Futures Options. However, if you trade Futures Options, or a mix of the two, you could always set the value to a variable all the same. Then, we can set a strike_request variable equal to the request.

def secdefStrikes(underConid,month):
  snapshot = float(snapshotData(underConid))
  itmStrikes = []
  url = f'https://localhost:5001/v1/api/iserver/secdef/strikes?conid={underConid}&secType=OPT&month={month}'
  strike_request = requests.get(url=url, verify=False)

With the request out of the way, we can start working with the response. I’ll begin with a strikes variable set to the “put” key. In most scenarios, the list of Put strikes will match those of the Calls; however, this is not always the case. Users interested in trading both rights or Calls specifically can adjust their code accordingly. Now we can begin to iterate through each strike in strikes. My condition for contracts in the money will be those within $20 of our contract, and so I will make an ‘if’ statement to retrieve all strikes within $10 under our snapshot price, and everything $10 over our snapshot price. Developers may alternatively implement a 5% variable rather than a fixed value to approach a similar result. Now we’ll start to append our results to the itmStrikes list we had created before, and then return the list once our loop has finished.

strikes = strike_request.json()["put"]
  for strike in strikes:
    if strike>snapshot-10 and strike<snapshot+10:
      itmStrikes.append(strike)
  return itmStrikes

Now we can move back to our Name-Main idiom before writing our last methods. After setting our month variable, I will set a new itmStrikes variable equal to a call to our secdefStrikes method with our underConid and month variables. Next, we can create a dictionary, contractDict. Then, we’ll iterate through each strike in itmStrikes. Here, we can set the strike as the key of contractDict, and set it equivalent to the response of a new secdefInfo method we’re about to write, taking the underConid, month, and strike variables we’ve retrieved up to this point.

itmStrikes = secdefStrikes(underConid,month)
  contractDict = {}
  for strike in itmStrikes:
    contractDict[strike] = secdefInfo(underConid,month,strike)

At this point, we can write out the secdefInfo method, taking the three arguments mentioned before, underConid, month, and strike. We’ll create another url variable set to the /iserver/secdef/info endpoint which takes all of our arguments as parameters, and the additional secType and right parameter, which we’ll preset to “OPT” and “P” respectively. Then we can follow this with setting the info_request variable to our request.get(url=url, verify=False) call once again.

def secdefInfo(conid, month, strike):
  url = f'https://localhost:5001/v1/api/iserver/secdef/info?conid={conid}&month={month}&strike={strike}&secType=OPT&right=P'
  info_request = requests.get(url=url, verify=False)

Before handling the response, I will first create an empty list, contracts. Now, the response to this endpoint will produce several different contracts as there are multiple expiries within the month. Given my scenario trades each weekly contract, I can retrieve each May contract. However, you are welcome to filter by a given MaturityDate, or other field that you feel is relevant. With my scope settled, I can iterate through our info_request.json() response using individual contract variables for each. Within the for-loop, we can assign the contract, symbol, strike, and maturityDate values to a contractDetails dictionary definition. I’ll then append this value to our contracts list. Once the loop has finished, I’ll then return the full contracts list.

for contract in info_request.json():
    contractDetails = {"conid": contract["conid"], 
                       "symbol": contract["symbol"],
                       "strike": contract["strike"],
                       "maturityDate": contract["maturityDate"]
                      }
    contracts.append(contractDetails)
  return contracts

With the larger script completed, we could simply print our resulting contractDict, or pass it around our trading session if we chose to run this every morning possible. However, as we mentioned at the start of the article, the intention of this is to elaborate on how to use these requests in the long term. We can look to add our dictionary into a CSV file for future use. I would encourage readers to extrapolate on this idea potentially with a larger internal database structure, or those empowered by Excel may choose to connect with xlwings as we discussed in our prior lesson. We’ll conclude our idiom with a call to a new writeResult method sending our contractDict variable as our argument.

writeResult(contractDict)

Within our writeResult method, we can start with our header values, that we had taken from the /info endpoint: conid, symbol, strike, and maturityDate. Then, I can write out a filePath to our current directory into a file named “./MayContracts.csv”.

def writeResult(contractDict):
  headers = ["conid", "symbol", "strike", "maturityDate"]
  filePath = "./MayContracts.csv"

Now we can create some general structures using the csv library. We’ll start with a contract_csv_file variable, set to open(filePath, ‘w’, newline=’’). This way we can reference a file, defined by our filePath variable that we’ll be writing to. Then, we can set a contract_writer variable exual to the csv.DictWriter object class. We’ll pass in our contract_csv_file as the “f” variable, and set the fieldnames variable equal to our headers. Then, I’d like to write out our headers using the DictWriter.writeheader method.

contract_csv_file = open(filePath, 'w', newline='')
  contract_writer = csv.DictWriter(f=contract_csv_file, fieldnames=headers)
  contract_writer.writeheader()

With our structure set, let’s iterate through each strikeGroup in our contractDict dictionary. Nesting further down, we can then iterate through each contractDetail inside the contractDict’s strikeGroup. This will allow us to write out the contractDetails with the contract_writer. We can conclude the method by closing out the file once we have finished iterating through our dictionary. I will add a simple print to indicate “Job’s done.”, as none of the other requests handled so far have actually been displayed to the user.

for strikeGroup in contractDict:
    for contractDetails in contractDict[strikeGroup]:
      contract_writer.writerow(contractDetails)
  contract_csv_file.close()
  print("Job's done.")

Thus the file is settled correctly. Assuming everything was saved correctly, we should now see the MayContracts.csv file stored in the same directory as where our python was executed. Opening the file, we’ll find that we now have a column displaying each conid, symbol, strike, and expiration date. Going forward, you could conversely utilize the csv.DictReader() class to pull out these same values as necessary whenever needed, and we can look to use them throughout the month as our trading persists. 

import requests
import urllib3

import csv

# Ignore insecure error messages
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def secdefSearch(symbol, listingExchange):

  url = f'https://localhost:5001/v1/api/iserver/secdef/search?symbol={symbol}'

  search_request = requests.get(url=url, verify=False)
  for contract in search_request.json():
    if contract["description"] == listingExchange:
      underConid = contract["conid"]

      for secType in contract["sections"]:
         if secType["secType"] == "OPT":
            months = secType["months"].split(';')

  return underConid,months

def secdefStrikes(underConid,month):

  snapshot = float(snapshotData(underConid))
  itmStrikes = []

  url = f'https://localhost:5001/v1/api/iserver/secdef/strikes?conid={underConid}&secType=OPT&month={month}'

  strike_request = requests.get(url=url, verify=False)

  strikes = strike_request.json()["put"]
  for strike in strikes:
    if strike>snapshot-10 and strike<snapshot+10:
      itmStrikes.append(strike)
  return itmStrikes

def secdefInfo(conid, month, strike):

  url = f'https://localhost:5001/v1/api/iserver/secdef/info?conid={conid}&month={month}&strike={strike}&secType=OPT&right=P'

  info_request = requests.get(url=url, verify=False)

  contracts = []

  for contract in info_request.json():
    contractDetails = {"conid": contract["conid"], 
                       "symbol": contract["symbol"],
                       "strike": contract["strike"],
                       "maturityDate": contract["maturityDate"]
                      }
    contracts.append(contractDetails)
  return contracts

def snapshotData(underConid):
  url = f'https://localhost:5001/v1/api/iserver/marketdata/snapshot?conids={underConid}&fields=31'
  requests.get(url=url, verify=False)
  snapshot = requests.get(url=url, verify=False)
  return snapshot.json()[0]["31"]

def writeResult(contractDict):
  headers = ["conid", "symbol", "strike", "maturityDate"]
  filePath = "./MayContracts.csv"
  contract_csv_file = open(filePath, 'w', newline='')
  contract_writer = csv.DictWriter(f=contract_csv_file, fieldnames=headers)
  contract_writer.writeheader()
  for strikeGroup in contractDict:
    for contractDetails in contractDict[strikeGroup]:
      contract_writer.writerow(contractDetails)
  contract_csv_file.close()
  print("Job's done.")

if __name__ == "__main__":
  # I'm looking for the U.S. Apple Incorporated company listed on NASDAQ
  underConid,months = secdefSearch("AAPL", "NASDAQ")
  
  # I only want the front month. 
  # Users could always grab all months, or pull out a specific value, but sending the 0 value always gives me the first available contract.
  month = months[0]

  # We'll be calling our Strikes endpoint to pull in the money strike prices rather than all strikes.
  itmStrikes = secdefStrikes(underConid,month)

  # We can then pass those strikes to the /info endpoint, and retrieve all the contract details we need.
  contractDict = {}
  for strike in itmStrikes:
    contractDict[strike] = secdefInfo(underConid,month,strike)

  writeResult(contractDict)

Join The Conversation

If you have a general question, it may already be covered in our FAQs. If you have an account-specific question or concern, please reach out to Client Services.

Leave a Reply

Your email address will not be published. Required fields are marked *

Disclosure: Interactive Brokers

The analysis in this material is provided for information only and is not and should not be construed as an offer to sell or the solicitation of an offer to buy any security. To the extent that this material discusses general market activity, industry or sector trends or other broad-based economic or political conditions, it should not be construed as research or investment advice. To the extent that it includes references to specific securities, commodities, currencies, or other instruments, those references do not constitute a recommendation by IBKR to buy, sell or hold such investments. This material does not and is not intended to take into account the particular financial conditions, investment objectives or requirements of individual customers. Before acting on this material, you should consider whether it is suitable for your particular circumstances and, as necessary, seek professional advice.

The views and opinions expressed herein are those of the author and do not necessarily reflect the views of Interactive Brokers, its affiliates, or its employees.

Disclosure: API Examples Discussed

Throughout the lesson, please keep in mind that the examples discussed are purely for technical demonstration purposes, and do not constitute trading advice. Also, it is important to remember that placing trades in a paper account is recommended before any live trading.

Disclosure: Options Trading

Options involve risk and are not suitable for all investors. Multiple leg strategies, including spreads, will incur multiple commission charges. For more information read the "Characteristics and Risks of Standardized Options" also known as the options disclosure document (ODD) or visit ibkr.com/occ

Disclosure: Order Types / TWS

The order types available through Interactive Brokers LLC's Trader Workstation are designed to help you limit your loss and/or lock in a profit. Market conditions and other factors may affect execution. In general, orders guarantee a fill or guarantee a price, but not both. In extreme market conditions, an order may either be executed at a different price than anticipated or may not be filled in the marketplace.

Disclosure: Futures Trading

Futures are not suitable for all investors. The amount you may lose may be greater than your initial investment. Before trading futures, please read the CFTC Risk Disclosure. A copy and additional information are available at ibkr.com.

IBKR Campus Newsletters

This website uses cookies to collect usage information in order to offer a better browsing experience. By browsing this site or by clicking on the "ACCEPT COOKIES" button you accept our Cookie Policy.