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