areed1192 / td-ameritrade-python-api

Unofficial Python API client library for TD Ameritrade. This library allows for easy access of the Standard API and allows users to build data pipelines for the Streaming API.
MIT License
684 stars 252 forks source link

get_options_chain returning -999.0 for greeks #164

Closed Dro92 closed 3 years ago

Dro92 commented 3 years ago

I don't know if this is an actual issue or more a function of the TD API/servers. From the image below it appears it's just a functionality of the TD API, but I figured I'd post this in case someone else comes across it. Not sure if this has come up before, if so, please ignore and delete; I couldn't find any prior commentary on the subject.

options_on_weekend

When doing a simple query using get_options_chain I noticed that all of the greeks, for every strike price, are reporting back a value of -999.0. Example snippet below shows the issue which impacts ALL greeks across all available strike prices:

                "tradeTimeInLong": 1614350529045,
                "quoteTimeInLong": 1615582795743,
                "netChange": 0.0,
                "volatility": -999.0,
                "delta": -999.0,
                "gamma": -999.0,
                "theta": -999.0,
                "vega": -999.0,
                "rho": -999.0,
                "openInterest": 435,

Attached are (2) JSON formatted file for reference. Note the extention was renamed to *.TXT to be able to upload.

It seems lilke this is less an issue with code and more the API to TD's server not responding back with valid data outside of market hours? I couldn't find any documentation about this on their developer website and figured I would at least inform the community to see if someone has come across this before.

aapl_good.txt aapl_bad.txt

scouncill commented 3 years ago

Do you get the same result when the exchange is open? I’ve had issues with data not being complete when the market is closed.

On Mar 14, 2021, at 6:24 PM, Dro92 @.***> wrote:

 I don't know if this is an actual issue or more a function of the TD API/servers. From the image below it appears it's just a functionality of the TD API, but I figured I'd post this in case someone else comes across it. Not sure if this has come up before, if so, please ignore and delete; I couldn't find any prior commentary on the subject.

When doing a simple query using get_options_chain I noticed that all of the greeks, for every strike price, are reporting back a value of -999.0. Example snippet below shows the issue which impacts ALL greeks across all available strike prices:

            "tradeTimeInLong": 1614350529045,
            "quoteTimeInLong": 1615582795743,
            "netChange": 0.0,
            "volatility": -999.0,
            "delta": -999.0,
            "gamma": -999.0,
            "theta": -999.0,
            "vega": -999.0,
            "rho": -999.0,
            "openInterest": 435,

Attached are (2) JSON formatted file for reference. Note the extention was renamed to *.TXT to be able to upload.

aapl_good. txt Generated during market hours and you can see there are actual values rather than an erroneous -999.0

appl_bad.txt Generated 2021/03/14 (Sunday) outside of markets hours and you can see there are all erroneous values.

It seems lilke this is less an issue with code and more the API to TD's server not responding back with valid data outside of market hours? I couldn't find any documentation about this on their developer website and figured I would at least inform the community to see if someone has come across this before.

aapl_good.txt aapl_bad.txt

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

Dro92 commented 3 years ago

I'm away from my PC currently, but I believe if you download the aapl_good.txt and search for 'delta' and scroll through the clause you'll see that some values are -999.99 even on a trading day; although I think it was just a handful of entries out of literal thousands.

However if what I observed above is true, I don't think it has anything to do with the client, but rather the actual TD Ameritrade server providing back unavailable information.

My intent is to work on a parser and in all cases now I know to account for a -999.99 condition meaning "no data" available.

Do you get the same result when the exchange is open? I’ve had issues with data not being complete when the market is closed. On Mar 14, 2021, at 6:24 PM, Dro92 @.**> wrote:  I don't know if this is an actual issue or more a function of the TD API/servers. From the image below it appears it's just a functionality of the TD API, but I figured I'd post this in case someone else comes across it. Not sure if this has come up before, if so, please ignore and delete; I couldn't find any prior commentary on the subject. When doing a simple query using get_options_chain I noticed that all of the greeks, for every strike price, are reporting back a value of -999.0. Example snippet below shows the issue which impacts ALL greeks across all available strike prices: "tradeTimeInLong": 1614350529045, "quoteTimeInLong": 1615582795743, "netChange": 0.0, "volatility": -999.0, "delta": -999.0, "gamma": -999.0, "theta": -999.0, "vega": -999.0, "rho": -999.0, "openInterest": 435, Attached are (2) JSON formatted file for reference. Note the extention was renamed to .TXT to be able to upload. aapl_good. txt Generated during market hours and you can see there are actual values rather than an erroneous -999.0 appl_bad.txt Generated 2021/03/14 (Sunday) outside of markets hours and you can see there are all erroneous values. It seems lilke this is less an issue with code and more the API to TD's server not responding back with valid data outside of market hours? I couldn't find any documentation about this on their developer website and figured I would at least inform the community to see if someone has come across this before. aapl_good.txt aapl_bad.txt — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

Dro92 commented 3 years ago

After further investigating I believe I'm going to close this "issue" out as there is nothing wrong with the API and it's more so an erroneous data handling condition for the user.

I also wanted to share this with the community as it is a data parser I came up with to navigate the nested JSON formatted options chain data pulled using areed1192's API. I'm developing this to be automated, but the parsing and data packaging schema fully work and I've tested it

s ability to write data to an InfluxDB 1.8.4 database running on a Raspberry Pi4.

As it currently stands the below code does the following:

import json
import pprint
import time
from influxdb_client import InfluxDBClient, WriteOptions
from influxdb_client.client.write_api import SYNCHRONOUS
from influxdb_config import USERNAME, PASSWORD, HOST, DATABASE  # Import sensitive info. from configuration file

database = DATABASE
retention_policy = 'autogen'

bucket = f'{database}/{retention_policy}'   # Investigate defaults

# If using InfluxDB v1.8.x with this API configure per https://github.com/influxdata/influxdb-client-python/blob/master/examples/influxdb_18_example.py
# Leave org as '-'
client = InfluxDBClient(url=HOST, token=f'{USERNAME}:{PASSWORD}', org='-')

#*************************************
#
#   DATA PARSER SECTION
#
#*************************************

start = time.process_time() #   Start timer

data = json.load(open('aapl.json'))

#   Option Chain parameters returned by TD Ameritrade opt_chain API
#   Comment out the parameters which you want to ignore
removeParams = [
    #'putCall',
    'symbol',
    'description',
    'exchangeName',
    #'bid',
    #'ask',
    #'last',
    #'mark',
    #'bidSize',
    #'askSize',
    "bidAskSize",
    #'lastSize',
    #'highPrice',
    #'lowPrice',
    'openPrice',
    'closePrice',
    'totalVolume',
    'tradeDate',
    'tradeTimeInLong',
    #'quoteTimeInLong',
    'netChange',
    'volatility',
    #"delta",
    #"gamma",
    #"theta",
    #"vega",
    #"rho",
    #"openInterest"
    'timeValue',
    'theoreticalOptionValue',
    'theoreticalVolatility',
    'optionDeliverablesList',
    #'strikePrice',
    #'expirationDate',
    'daysToExpiration',
    'expirationType',
    'lastTradingDay',
    #'multiplier',
    'settlementType',
    'deliverableNote',
    'isIndexOption',
    #'percentChange',
    'markChange',
    'markPercentChange',
    'nonStandard',
    #'inTheMoney',
    'mini'
]

def checkList(ele, prefix):

    for i in range(len(ele)):

        #print('Parse the below list, keep key:values wanted in new list and send to database')
        #print(ele)  # [{Key:value}] need to be able to get to the dictionary, scan and keep what is desired

        for x in ele:    
            keepParams = ele[0] # For address 0 in list ele - only dict {} entry in the list

            # Go through the dictionary and remove the keys defined in removeParams
            [keepParams.pop(key) for key in removeParams]

            #-----------------------------------------------------------------------------------------
            # InfluxDB Line Protocl format https://github.com/influxdata/influxdb-client-python#writes
            #
            # "measurement_value, tag_name = tag value field_name=field_value time_value"
            #
            #  measurement_value = expirationDate                               <- Value in unix epoch time
            #  tag set:     tag_name = contract, tag_value = putCall            <- Value in string, either PUT or CALL
            #  field set:   field_name = fiel_key, field_value = field_value    <- Values can vary depending on data
            #  time:        time_value = quoteTimeInLong                        <- Value integer in unix epoch time

            # Possibly make this dynamic by automating the pulling of key and value pairs for the field sets?

            dataParsed = str(#str(keepParams.get('expirationDate'))+","+
                                "aapl,"             
                                "contract="+keepParams.get('putCall')
                                +" strikePrice="+str(keepParams.get('strikePrice'))
                                +",bid="+str(keepParams.get('bid'))
                                +",ask="+str(keepParams.get('ask'))
                                +",last="+str(keepParams.get('last'))
                                +",mark="+str(keepParams.get('mark'))
                                #+",bidsize="+str(keepParams.get('bidsize'))
                                +" "+str(keepParams.get('quoteTimeInLong'))
                            )            

            dataInfluxDB.append(dataParsed)

        if (isinstance(ele[i], list)):
            checkList(ele[i], prefix+"["+str(i)+"]")
            # Not used

        else:
            checkDict(ele[i], prefix+"["+str(i)+"]")      

def checkDict(jsonObject, prefix):

    for ele in jsonObject:
        if (isinstance(jsonObject[ele], dict)):
            checkDict(jsonObject[ele], prefix+"."+ele)
        elif (isinstance(jsonObject[ele], list)):

            checkList(jsonObject[ele], prefix+"."+ele)         
            # Clear Map/List before going into the next strike price

        elif (isinstance(jsonObject[ele], (str, float, int, type(None)))):
            # Verify if any dictionary entries are of type string, float, integer or null
            # Null check needed because TD will sometimes report 'null' when data not available
            pass

#
#   CHECK INFLUXDB SERVER CONNECTION
#

print("Conduct connection Health Check prior to parse: {}".format(client.health()))

#
#   BEGIN DATA PARSING AND SEND ITERATIONS
#

write_api = client.write_api(write_options=SYNCHRONOUS) # If using asynchronouos, more work is needed

for element in data: # element are the entries in the TOP LEVEL JSON data object

    dataInfluxDB=[] # List for data batch write - should not exceed 5000 points

    # If JSON Field value is a Nested JSON
    if (isinstance(data[element], dict)):
        checkDict(data[element], element)

        # This loop will go through every expiration date and strike price in the putExpDateMap and then callExpDateMap
        # A batch write of data will occur in this loop to package all the "PUT" data, send and then repeat for "CALL" data
        write_api.write(bucket=bucket, org='-', record=dataInfluxDB, write_precision='ms')

    # If JSON Field value is a string, float or integer. This will print the immediate key:value data in the first level object
    elif (isinstance(data[element], (str, float, int, type, type(None)))): 
        pass

#   
#   CLOSE INFLUXDB SERVER CONNECTION
#

print("Result of connection close is: {}".format(client.close()))
print(time.process_time()-start)   # Report total time