Search code examples
async-awaitnonetypepython-3.9

Zip & Map Asynchronous Gather Python3.9


I'm trying to get historical stock prices from the Polygon.io endpoint on the ex-dividend and the day before the ex-dividend date and map those values to new columns in a panda's dataframe.

I have a separate class that populates the prior business day in events when the prior business date falls within a trading holiday as (BDay).

I am also removing instances where the response object from Polygon does not have two results.

When running the code below, I get a TypeError: 'NoneType' object is not iterable on the zip await portion of the code. I've tried printing the results of the calls and see that I am correctly getting float values from the asynchronous function. I've also tried printing the types to confirm that the nonetype isn't being set before the main function attempts to extract the data from the tuple. What am I doing wrong?

Full disclosure, I'm new to using asych. Also, the majority of this work is in a Jupyter notebook so I'm not sharing the full code base here, just the section that is generating the error.

from datetime import datetime
from pandas.tseries.offsets import BDay

async def get_history(ticker, prior_business_date, ex_dividend_date):
    '''
        Receives the ticker, prior business date and ex-dividend date from the caller as a string
        First checks to see if the prior business date (date before Ex-Dividend is a holidy
        If the date is a holiday, subtracts one day from prior business date
        Function then assigns to value x the prior candlestick date for the corrected date
        If the date IS NOT a holiday, the function gets the prior candlestick data based on the prior business date
        Function also returns candlestick data on the ex-dividend date before returning asynchronously to the caller
    '''
    if prior_business_date in get_trading_close_holidays(int(prior_business_date.split('-')[0])):
        #set the prior_business date to datetime object
        #deduct 1 day from prior_business_date to get useable data
        prior_business_date = datetime.strftime(datetime.strptime(prior_business_date,'%Y-%m-%d').date() - BDay(1), '%Y-%m-%d')
        #print(prior_business_date) #debug
    url = 'https://api.polygon.io/v2/aggs/ticker/{}/range/1/day/{}/{}?sort=dsc&limit=10'.format(ticker, prior_business_date, ex_dividend_date)
    r = requests.get(url, headers=headers).json()
    #print(r['ticker'], r['queryCount']) #debug
    if (r['queryCount'] < 2) or (r == None):
        pass
    else:
        #print(type(r)) #debug
        x = r['results'][0]['c'] #first data set in results is prior date; get only close
        y = r['results'][1]['c'] #second data set in results is ex-dividend date get only close
        #print(type(x),type(y)) #debug
        return x, y

async def main():
    high_volume['prior_biz_close'], high_volume['xdiv_close'] = zip(*await asyncio.gather(*map(get_history,high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date'])))
        
asyncio.run(main())

Here is the response I receive when attempting to run the code:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [52], line 56
     53     high_volume['prior_biz_close'], high_volume['xdiv_close'] = zip(*await asyncio.gather(*map(get_history,high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date'])))
     54         #*(get_history(x,y,z) for x,y,z in (df['ticker'], df['prior_business_date'], df['ex_dividend_date']))))
---> 56 asyncio.run(main())

File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/nest_asyncio.py:35, in _patch_asyncio.<locals>.run(main, debug)
     33 task = asyncio.ensure_future(main)
     34 try:
---> 35     return loop.run_until_complete(task)
     36 finally:
     37     if not task.done():

File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/nest_asyncio.py:90, in _patch_loop.<locals>.run_until_complete(self, future)
     87 if not f.done():
     88     raise RuntimeError(
     89         'Event loop stopped before Future completed.')
---> 90 return f.result()

File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/futures.py:201, in Future.result(self)
    199 self.__log_traceback = False
    200 if self._exception is not None:
--> 201     raise self._exception
    202 return self._result

File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py:256, in Task.__step(***failed resolving arguments***)
    252 try:
    253     if exc is None:
    254         # We use the `send` method directly, because coroutines
    255         # don't have `__iter__` and `__next__` methods.
--> 256         result = coro.send(None)
    257     else:
    258         result = coro.throw(exc)

Cell In [52], line 53, in main()
     52 async def main():
---> 53     high_volume['prior_biz_close'], high_volume['xdiv_close'] = zip(*await asyncio.gather(*map(get_history,high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date'])))

TypeError: 'NoneType' object is not iterable

Solution

  • !!!!SOLVED!!!!

    I resolved this by aggregating the data the I was sending to the asynchronous function into one data set and then parsing that data within the function instead of trying to send multiple parameters from async main.

    Here's what I did:

    1. removed the await gather function
    2. looped through the ticker data
    3. assign the prior date, ex-dividend date and ticker to list value within a list.
    4. loop through the list and send to the asynchronous function to call the Polygon API and retrieve results
    5. assign results data to new list to be incorporated into the dataframe
    6. capture index of bad data for removal from the dataframe

    I recognize I didn't provide a great deal of context on my earlier ask, yet here's my code in the event it helps anyone else.

    from datetime import datetime
    from pandas.tseries.offsets import BDay
    
    prior_biz_close = []
    xdiv_close = []
    to_drop = []
    
    '''
        to_drop likely includes many days where the business day provided falls on a weekend. Need to update to 
        allow for conversion of incorrect days to working days.
    '''
    
    async def get_history(data, idx):
        '''
            Receives the ticker, prior business date and ex-dividend date from the caller as a string
            First checks to see if the prior business date (date before Ex-Dividend is a holidy
            If the date is a holiday, subtracts one day from prior business date
            Function then assigns to value x the prior candlestick date for the corrected date
            If the date IS NOT a holiday, the function gets the prior candlestick data based on the prior business date
            Function also returns candlestick data on the ex-dividend date before returning asynchronously to the caller
        '''
        #print(data)
        if data[1] in get_trading_close_holidays(int(data[1].split('-')[0])):
            #set the prior_business date to datetime object
            #deduct 1 day from prior_business_date to get useable data
            data[1] = datetime.strftime(datetime.strptime(data[1],'%Y-%m-%d').date() - BDay(1), '%Y-%m-%d')
            
        url = 'https://api.polygon.io/v2/aggs/ticker/{}/range/1/day/{}/{}?sort=dsc&limit=10'.format(data[0], data[1], data[2])
        r = requests.get(url, headers=headers).json()
        
        if (r['queryCount'] < 2) or (r == None):
            to_drop.append(idx)
            pass
        else:
            #print(type(r))
            prior_biz_close.append(r['results'][0]['c']) #first data set in results is prior date; get only close
            xdiv_close.append(r['results'][1]['c']) #second data set in results is ex-dividend date get only close
            #print(type(x),type(y))
    
    async def main():
        result = [(ticker, prior_business_date, ex_dividend_date) for (ticker, prior_business_date, ex_dividend_date) in zip(high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date'])]
        for i in result:
            await get_history(list(i), result.index(i))
            
    asyncio.run(main())