"""
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