Back to Community
Sneak Peek: Using a Quantopian Research Notebook to Analyze Share Buyback Data

About 4 months ago, we announced that we were hard at work building a hosted research platform for analyzing our curated datasets, your Quantopian algorithms, and your backtest results.

For the first time, we want to share a notebook here in the forums to give you a sneak peek into how the research platform is progressing and more importantly how it can help you craft better trading algorithms.

We collaborated with EventVestor, a data vendor who provides information about corporate events. Specifically, EventVestor has provided us with data on share buyback announcements. In the notebook, we explore the idea of a post-share buyback announcement drift in stocks. We load raw data from EventVestor into the notebook and dig into the various attributes provided by the data to better understand our options when building an algorithm. With that knowledge in hand, having built an algorithm to use the data, we then explore the results from each variation.

Below is a chart from the notebook that summarizes our findings. We're able to optimize our parameters through the course of the notebook. We find the optimal combination of variables for maximizing returns, maximizing Sharpe ratio and minimizing drawdown.

Click the "VIEW NOTEBOOK" button and scroll through the notebook to see our step by step process for testing and optimizing our strategy.

The algorithm used for this analysis is found on this thread, please scroll down to find the backtest.

The sample data in this algorithm provides a static snapshot between January 2007 - January 2015. That being said, fetch_eventvestor is not allowed in Live Trading and your algorithm will throw an error if you try to deploy it in either paper, context, or live trading modes!

We are working to incorporate this data in a more permanent fashion but until then, you can contact EventVestor for more information.

If you'd like to look at the drift found in this event, you can find the followup event study here.

Loading notebook preview...
Notebook previews are currently unavailable.
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

15 responses

Perhaps this event study is similar to Balch's insider trading analysis.

http://augmentedtrader.com/2013/08/12/assessment-of-insider-trading-information-for-investment-strategies/

I hope that "Clone this notebook" will be a next feature.

Would be interested to see results when taking into account relevant benchmarks in order to see excess returns over benchmark and thus to get a handle on the strategy's alpha.

Hi all,

The algorithm that we used for the backtest is included here. We use a custom fetch_eventvestor method that will get you a static set of data from 2007 up till January, 2015.

There are a number of different parameters that you can change in order to fiddle around with the algorithm:

  • context.days_to_hold_positions - The number of days to enter into a position before exiting
  • context.hedge_lookback - The number of days to look back before calculating our beta hedge (SPY)
  • In choose strategy, you have a number of different lines that you can comment/uncomment to turn on the different parts of the strategy found in the notebook.

Notes:

The sample data in this algorithm provides a static snapshot between January 2007 - January 2015. That being said, fetch_eventvestor is not allowed in Live Trading and your algorithm will throw an error if you try to deploy it in either paper, context, or live trading modes!

We are working to incorporate this data in a more permanent fashion but until then, you can contact EventVestor for more information.

Clone Algorithm
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""

Our friends over at EventVestor have given us a cut of historical share buyback announcements data. Share buybacks, much like earnings announcements, have the potential to provide a stock price drift on the trading date after the announcement. We attempt to create an algorithm that will capture the alpha from just that in this algorithm.

PSBAD - Post Share Buybacks Announcement Drift

If you want an in-depth breakdown and analysis of the data, please checkout the Research notebook share. We go over exactly what the data looks like and it's general composition. 

THE ALGORITHM:

We'll be capturing the post share buyback announcement drift by entering into a position of a company the trade date following the announcement, holding the position for 7 days, and exiting after 7 days. All positions will be long. 

A few notes:
    - We're hedging our positions based on a beta hedge
    - You can change any of the variables found in `initialize` to tinker with the different holding periods and the number of days used for the Beta Hedge Calculation
    - EventVestor conveniently attaches a column called 'Trade Date' that indicates which day we should be trading our data. 

"""

import pandas as pd
import numpy as np

def initialize(context):
    #: Tracking the number of days to hold a positions
    context.days_to_hold_positions = 7
    
    #: Tracking the number of days that have passed for each security that we hold
    context.stocks_held_and_days = {}
    
    #: Tracking our daily universe sids and size
    context.universe_sids = []
    context.universe_size = 0
    
    #: Set the weights that I want to order for each security
    context.stock_target_value = 0
    
    #: Hedge remaining exposure to create a dollar neutral strategy
    context.spy = sid(8554)
    context.hedge_lookback = 30
    
    #: Set our universe of stocks to be our CSVs
    #: A temporary function Quantopian has added to the API to access EventVestor's data
    fetch_eventvestor('eventvestor_buybacks',
                      date_column='trade_date',
                      pre_func=choose_strategy,
                      universe_func=my_universe)
    
    #: Track our daily universe size
    schedule_function(track_universe_size_and_stock_weight,
                      date_rules.every_day(),
                      time_rules.market_open())
    
    #: Set our schedule_function for any postions where we enter in
    schedule_function(enter_positions,
                      date_rules.every_day(),
                      time_rules.market_open())
    
    #: Track the days that have passed
    schedule_function(track_days,
                      date_rules.every_day(),
                      time_rules.market_close(minutes=30))
   
    #: Set our schedule_function for any positions we need to exit in
    schedule_function(exit_positions,
                      date_rules.every_day(),
                      time_rules.market_close(minutes=5))

def choose_strategy(fetcher_data):
    """
    Here, you can comment and uncomment certain criteria to test out the different strategies
    presented in the notebook.
    """
    #: Buying on buybacks greater than 7.5%
    fetcher_data = fetcher_data[(fetcher_data['pct_of_tso_tobuy'] > 7.5)]
    
    #: Buying on buybacks between 2.5 and 7.5%
    # fetcher_data = fetcher_data[((fetcher_data['pct_of_tso_tobuy'] > 2.5) & (fetcher_data['pct_of_tso_tobuy'] < 7.5))]
    
    #: Buying only on New Buybacks
    # fetcher_data = fetcher_data[(fetcher_data['buyback_type'] == "New")]
    
    #: Buying only on Additional Buybacks
    # fetcher_data = fetcher_data[(fetcher_data['buyback_type'] == "Additional")]
    
    #: Buying only on the major sectors
    # fetcher_data = fetcher_data[((fetcher_data['sector'] == 'FINANCIAL') | (fetcher_data['sector'] == 'SERVICES') | (fetcher_data['sector'] == 'TECHNOLOGY'))]
    
    return fetcher_data
    
def track_universe_size_and_stock_weight(context, data):
    """
    Track our daily universe size and target values for our securities

    - I'm going to remove any positions that I already hold so we start with a clean slate
    """
    #: Setting up our universe size
    actionable_universe = []
    for stock in context.universe_sids:
        if stock not in context.stocks_held_and_days:
            actionable_universe.append(stock)
            
    #: On any given day, I'll only be investing 1/days_held of my available cash
    current_cash = context.portfolio.cash * (1.0/context.days_to_hold_positions)
    
    #: Setting up our target values
    percent_weight = 1.0/(len(actionable_universe)) if len(actionable_universe) > 0 else 0
    target_value = current_cash * percent_weight
        
    #: Setting our context variables
    context.stock_target_value = target_value
    context.universe_size = len(actionable_universe)
    
def track_days(context, data):
    """
    Tracks the number of days that we've held each security
    """
    for stock in context.stocks_held_and_days:
        context.stocks_held_and_days[stock]['days'] += 1
        
def trade_gate(context, data, stock):
    """
    Method to check whether we want to trade the security
    """
    if 'event_date' not in data[stock]:
        return False
    
    #: Making sure that the current day isn't far from the event date
    event_date = pd.to_datetime(data[stock]['event_date'], utc=True)
    if (get_datetime() - event_date).days <= 1 and \
        stock in data and \
        stock not in context.stocks_held_and_days and \
        'price' in data[stock]:
        return True
    else:
        return False
    
    
def enter_positions(context, data):
    """
    This method at the beginning of day will check our securities and enter their positions at the beginning of day
    """
    hedge_dollar_amount = 0
    price_history = history(context.hedge_lookback, '1d', 'price')
    for stock in data:
        if stock == sid(8554):
            continue
        if trade_gate(context, data, stock) == True:
            #: Ordering and keeping track of our current positions
            order_target_value(stock, context.stock_target_value)
            
            #: Calculate hedge
            beta_hedge_amt = calc_beta_hedge(context, data, stock, price_history)
            hedge_dollar_amount += beta_hedge_amt*context.stock_target_value
            
            #: Logging what we just did
            log.info("Ordering %s at %s for $%s with hedge_amt: $%s" % (stock.symbol, get_datetime(), context.stock_target_value, hedge_dollar_amount))
            
            context.stocks_held_and_days[stock] = {'days': 0, 'hedge_amount': hedge_dollar_amount}
            
    #: Calculate hedge
    order_target_value(context.spy, -hedge_dollar_amount)
    
    #: Recording
    record(number_of_positions=len(context.stocks_held_and_days))

def exit_positions(context, data):
    """
    Check if we've held a stock longer than our desired day amount and exit positions afterwards
    """
    stocks_exited = []
    total_dollar_amount_sold = 0
    for stock, info_dict in context.stocks_held_and_days.iteritems():
        days_held = info_dict['days']
        hedge_amount = info_dict['hedge_amount']
        if days_held >= context.days_to_hold_positions and stock in data:
            order_target_percent(stock, 0)
            stocks_exited.append(stock)
            log.info("Exiting positions on %s after %s days at %s" % (stock, days_held, get_datetime()))
            total_dollar_amount_sold += hedge_amount
    for stock in stocks_exited:
        del context.stocks_held_and_days[stock]
        
    #: Exit hedge positions
    order_target_value(sid(8554), total_dollar_amount_sold)

def calc_beta_hedge(context, data, stock, price_history):
    """
    Calculate our beta amounts for each security
    """
    stock_prices = price_history[stock].pct_change().dropna()
    bench_prices = price_history[context.spy].pct_change().dropna()
    aligned_prices = bench_prices.align(stock_prices,join='inner')
    bench_prices = aligned_prices[0]
    stock_prices = aligned_prices[1]
    bench_prices = np.array( bench_prices.values )
    stock_prices = np.array( stock_prices.values )
    bench_prices = np.reshape(bench_prices,len(bench_prices))
    stock_prices = np.reshape(stock_prices,len(stock_prices))
    m, b = np.polyfit(bench_prices, stock_prices, 1) 
    return m

def my_universe(context, fetcher_data):
    """
    Method for setting our universe of stocks which we will use to determine
    our weights for each security as well
    """
    #: Setting our universe of stocks
    sids = set(fetcher_data['sid'])  
    symbols = [s.symbol for s in sids] 
    log.info("Our daily universe size is %s sids" % len(symbols))
    context.universe_sids = list(sids)
    return sids
              
def handle_data(context, data):
    pass
There was a runtime error.

Say for example that ABC announces a buy-back, and you go long the position for say 20 days, and during those 20 days the stock went up 20% (annualized) while the S&P index went up 30%. DEF announced a buy back. You held the position for 20 days and DEF went up 8% while the S&P index went down 10%. Clearly DEF was the better trade, but is that captured in your analysis?

Abraham,

We're basing a lot of this algorithm off a previous event study, but I think you bring up a good point. The analysis we have above doesn't actually show the drift from the individual trade alone, rather it shows the effectiveness of the trading strategy using buybacks as a signal.

Would you be interested in seeing the event study using our same Research Notebook?

Seong

Yes please!

I find this to be very interesting. It would be great if Quantopian could make this a corporate event-like data feed available in production.

I attached a version that I think will improve the accuracy of the beta hedge (eg, shorting SPY) to better meet the objective of being both risk neutral and approximately cash neutral.

http://quant-coder.prokopyshen.com

Clone Algorithm
33
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""

Our friends over at EventVestor have given us a cut of historical share buyback announcements data. Share buybacks, much like earnings announcements, have the potential to provide a stock price drift on the trading date after the announcement. We attempt to create an algorithm that will capture the alpha from just that in this algorithm.

PSBAD - Post Share Buybacks Announcement Drift

If you want an in-depth breakdown and analysis of the data, please checkout the Research notebook share. We go over exactly what the data looks like and it's general composition. 

THE ALGORITHM:

We'll be capturing the post share buyback announcement drift by entering into a position of a company the trade date following the announcement, holding the position for 7 days, and exiting after 7 days. All positions will be long. 

A few notes:
    - We're hedging our positions based on a beta hedge
    - You can change any of the variables found in `initialize` to tinker with the different holding periods and the number of days used for the Beta Hedge Calculation
    - EventVestor conveniently attaches a column called 'Trade Date' that indicates which day we should be trading our data. 

"""

import pandas as pd
import numpy as np

def initialize(context):
    #: Tracking the number of days to hold a positions
    context.days_to_hold_positions = 7
    
    #: Tracking the number of days that have passed for each security that we hold
    context.stocks_held_and_days = {}
    
    #: Tracking our daily universe sids and size
    context.universe_sids = []
    context.universe_size = 0
    
    #: Set the weights that I want to order for each security
    context.stock_target_value = 0
    
    #: Hedge remaining exposure to create a dollar neutral strategy
    context.spy = sid(8554)
    context.hedge_lookback = 30
    
    #: Set our universe of stocks to be our CSVs
    #: A temporary function Quantopian has added to the API to access EventVestor's data
    fetch_eventvestor('eventvestor_buybacks',
                      date_column='trade_date',
                      pre_func=choose_strategy,
                      universe_func=my_universe)
    
    #: Track our daily universe size
    schedule_function(track_universe_size_and_stock_weight,
                      date_rules.every_day(),
                      time_rules.market_open())
    
    #: Set our schedule_function for any postions where we enter in
    schedule_function(enter_positions,
                      date_rules.every_day(),
                      time_rules.market_open())
    
    #: Track the days that have passed
    schedule_function(track_days,
                      date_rules.every_day(),
                      time_rules.market_close(minutes=30))
   
    #: Set our schedule_function for any positions we need to exit in
    schedule_function(exit_positions,
                      date_rules.every_day(),
                      time_rules.market_close(minutes=5))

def choose_strategy(fetcher_data):
    """
    Here, you can comment and uncomment certain criteria to test out the different strategies
    presented in the notebook.
    """
    #: Buying on buybacks greater than 7.5%
    fetcher_data = fetcher_data[(fetcher_data['pct_of_tso_tobuy'] > 7.5)]
    
    #: Buying on buybacks between 2.5 and 7.5%
    # fetcher_data = fetcher_data[((fetcher_data['pct_of_tso_tobuy'] > 2.5) & (fetcher_data['pct_of_tso_tobuy'] < 7.5))]
    
    #: Buying only on New Buybacks
    # fetcher_data = fetcher_data[(fetcher_data['buyback_type'] == "New")]
    
    #: Buying only on Additional Buybacks
    # fetcher_data = fetcher_data[(fetcher_data['buyback_type'] == "Additional")]
    
    #: Buying only on the major sectors
    # fetcher_data = fetcher_data[((fetcher_data['sector'] == 'FINANCIAL') | (fetcher_data['sector'] == 'SERVICES') | (fetcher_data['sector'] == 'TECHNOLOGY'))]
    
    return fetcher_data
    
def track_universe_size_and_stock_weight(context, data):
    """
    Track our daily universe size and target values for our securities

    - I'm going to remove any positions that I already hold so we start with a clean slate
    """
    #: Setting up our universe size
    actionable_universe = []
    for stock in context.universe_sids:
        if stock not in context.stocks_held_and_days:
            actionable_universe.append(stock)
            
    #: On any given day, I'll only be investing 1/days_held of my available cash
    current_cash = context.portfolio.cash * (1.0/context.days_to_hold_positions)
    
    #: Setting up our target values
    percent_weight = 1.0/(len(actionable_universe)) if len(actionable_universe) > 0 else 0
    target_value = current_cash * percent_weight
        
    #: Setting our context variables
    context.stock_target_value = target_value
    context.universe_size = len(actionable_universe)
    
def track_days(context, data):
    """
    Tracks the number of days that we've held each security
    """
    for stock in context.stocks_held_and_days:
        context.stocks_held_and_days[stock]['days'] += 1
        
def trade_gate(context, data, stock):
    """
    Method to check whether we want to trade the security
    """
    if 'event_date' not in data[stock]:
        return False
    
    #: Making sure that the current day isn't far from the event date
    event_date = pd.to_datetime(data[stock]['event_date'], utc=True)
    if (get_datetime() - event_date).days <= 1 and \
        stock in data and \
        stock not in context.stocks_held_and_days and \
        'price' in data[stock]:
        return True
    else:
        return False
    
    
def enter_positions(context, data):
    """
    This method at the beginning of day will check our securities and enter their positions at the beginning of day
    """
    price_history = history(context.hedge_lookback, '1d', 'price')
    for stock in data:
        if stock == sid(8554):
            continue
        if trade_gate(context, data, stock) == True:
            #: Ordering and keeping track of our current positions
            order_target_value(stock, context.stock_target_value)
            
            #: Calculate hedge
            beta_hedge_amt = calc_beta_hedge(context, data, stock, price_history)*context.stock_target_value
            
            #: Logging what we just did
            log.info("Ordering %s at %s for $%s with hedge_amt: $%s" % (stock.symbol, get_datetime(), context.stock_target_value, beta_hedge_amt))
            
            context.stocks_held_and_days[stock] = {'days': 0, 'hedge_amount': beta_hedge_amt}
            
    #: Calculate hedge by summing up all our holdings
    hedge_dollar_amount = 0
    for S in context.stocks_held_and_days:
        hedge_dollar_amount += context.stocks_held_and_days[S]['hedge_amount']
    order_target_value(context.spy, -hedge_dollar_amount)
    
    #: Recording
    record(number_of_positions=len(context.stocks_held_and_days))

def exit_positions(context, data):
    """
    Check if we've held a stock longer than our desired day amount and exit positions afterwards
    """
    stocks_exited = []
    for stock, info_dict in context.stocks_held_and_days.iteritems():
        days_held = info_dict['days']
        #->RJP hedge_amount = info_dict['hedge_amount']
        if days_held >= context.days_to_hold_positions and stock in data:
            order_target_percent(stock, 0)
            stocks_exited.append(stock)
            log.info("Exiting positions on %s after %s days at %s" % (stock, days_held, get_datetime()))
    for stock in stocks_exited:
        del context.stocks_held_and_days[stock]
        
    #: Calculate needed hedge position
    beta_needed=0
    for S in context.stocks_held_and_days:
        beta_needed += context.stocks_held_and_days[S]['hedge_amount'] 
    order_target_value(sid(8554), -beta_needed)

def calc_beta_hedge(context, data, stock, price_history):
    """
    Calculate our beta amounts for each security
    """
    stock_prices = price_history[stock].pct_change().dropna()
    bench_prices = price_history[context.spy].pct_change().dropna()
    aligned_prices = bench_prices.align(stock_prices,join='inner')
    bench_prices = aligned_prices[0]
    stock_prices = aligned_prices[1]
    bench_prices = np.array( bench_prices.values )
    stock_prices = np.array( stock_prices.values )
    bench_prices = np.reshape(bench_prices,len(bench_prices))
    stock_prices = np.reshape(stock_prices,len(stock_prices))
    m, b = np.polyfit(bench_prices, stock_prices, 1) 
    return m

def my_universe(context, fetcher_data):
    """
    Method for setting our universe of stocks which we will use to determine
    our weights for each security as well
    """
    #: Setting our universe of stocks
    sids = set(fetcher_data['sid'])  
    symbols = [s.symbol for s in sids] 
    log.info("Our daily universe size is %s sids" % len(symbols))
    context.universe_sids = list(sids)
    return sids
              
def handle_data(context, data):
    pass
There was a runtime error.

Richard,

We are definitely working in that direction! In the mean time, you can contact Anju from EventVestor to gain access to the data. I'll PM you his email address (or you can submit your name on the form at www.eventvestor.com/quanto)

EventVestor is very interested in providing this kind of data at prices that individuals can afford. We have used fetcher successfully with their data.

All the best,
Josh

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Hi all,

A followup event study has been conducted to this thread. Check out (https://www.quantopian.com/posts/research-looking-for-drift-an-event-study-with-share-buybacks-announcements) to see the results!

Seong

I found this writeup on zerohedge.com interesting given this algo's approach. Specifically the data on the volume of buybacks in the previous month: http://www.zerohedge.com/news/2015-02-28/here-reason-why-stocks-just-had-their-best-month-october-2011

Hi Seong, Thank you for this innovative algo. When I clone and build, I get an error message for line 211. Am i doing something wrong?

Could be a data issue.

Commenting out line 211 and inserting some print statements:

# symbols = [s.symbol for s in sids]  
print 'sids',type(sids),sids  
symbols=[]  
for s in sids:  
    print 's',type(s),s  
    symbols.append(s.symbol)

And then running 01/25/2007 to 02/02/2007.

On 2007-01-26 we see that the usual format of the fetcher_data['sid'] looks like this:

2007-01-26 PRINT sids
2007-01-26 PRINT type 'set'
2007-01-26 PRINT
set([Security(12160, symbol='COF', security_name='CAPITAL ONE FINANCIAL CORP', exchange='NEW YORK STOCK EXCHANGE', start_date=Timestamp('1994-11-16 00:00:00+0000', tz='UTC'), end_date=Timestamp('2015-04-01 00:00:00+0000', tz='UTC'), first_traded=None)])

But on 2007-02-01, the entry returned looks different:

2007-02-01PRINT sids
2007-02-01PRINT type 'set'
2007-02-01PRINT
set([0, Security(20163, symbol='WCC', security_name='WESCO INTERNATIONAL INC', exchange='NEW YORK STOCK EXCHANGE', start_date=Timestamp('1999-05-12 00:00:00+0000', tz='UTC'), end_date=Timestamp('2015-04-01 00:00:00+0000', tz='UTC'), first_traded=None)])

There is an integer of value zero as the first element in the set instead of the expected security object.

Richard
http://quant-coder.prokopyshen.com/ContactMe

Hi Easan and Richard,

I believe that this: There is an integer of value zero as the first element in the set instead of the expected security object. is the answer to the question. The best fix for now is to have the following line of code while we investigate the problem!

if 0 in sids: sids.remove(0)

A belated thank you, Richard and Seong.