Back to Community
[Update 6/14] How to conduct your own event study using Research

UPDATE 6/14: We've just released 'Cloning' for notebooks. If you have access to Research and are looking to see how you can conduct your very own event study, take a look at this notebook and post back with questions and comments.

Previously, we had released a notebook and algorithm that detailed using Share Buybacks Announcements as a trading signal. In this post, we're going to show you an event study conducted in the same Research Environment that takes an in-depth look at share buybacks announcements as a singular event. We collaborated with EventVestor, a data vendor who provides information about corporate events. Find more information about the data and how you can gain access to it by clicking here.

Below is a chart from the notebook that summarizes our findings from the study. In total, we found that while there is a large ~1% movement in stock prices immediately following a buybacks announcement (which most retail investors can't trade on), there is also a drift that follows for several days afterwards with average abnormal returns closing up on .5~1%. This abnormal drift is what we can create a strategy around and we did so here in this notebook and algorithm.

Click the "VIEW NOTEBOOK" button and scroll through the notebook. There's a lot of interesting findings here and I promise you're going to learn a lot.

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


4 responses

Hi all,

Here's the algorithm that inspired it all! 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.

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.

This particular algorithm was created by @Richard Prokopyshen who found an error in the hedging logic of my first algorithm. Thanks for the share Richard!

Clone Algorithm
Backtest from to with initial capital
Total Returns
Max Drawdown
Benchmark Returns
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. 


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
    #: Track our daily universe size
    #: Set our schedule_function for any postions where we enter in
    #: Track the days that have passed
    #: Set our schedule_function for any positions we need to exit in

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:
    #: On any given day, I'll only be investing 1/days_held of my available cash
    current_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
        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):
        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
  "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

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)
  "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
    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]"Our daily universe size is %s sids" % len(symbols))
    context.universe_sids = list(sids)
    return sids
def handle_data(context, data):
There was a runtime error.

Heya - I notice you seem to be setting the universe to the fetched data (from eventvestor?). Do you have a link to the evidence that eventvestor's data is survivorship-bias free? I am suspicious that the good results are only around the financial crisis...

Hi Simon,
EventVestor tracks all buyback announcements and to make sure the data is survivorship-bias free, we provide all data as reported, keeping all history even if the stock doesn't trade any more. The same data is provided to Quantopian for this research. We will soon be releasing more details about data on other key corporate events including CEO/CFO Changes, Clinical Trials, Dividends, Earnings Announcements, Executive Changes, FDA Filings/Approvals, Earnings Guidance, Issue Debt, Issue Equity, Insider Transactions, Activists and Hedge Funds Ownership Changes, Mergers & Acquisitions, Retail Same Store Sales, Spin-offs, and Stock Splits.

Anju Marempudi

Hey all,
