Quant BuffetRelax, Not Over Thinking

Market Neutral Strategy Based on Share Buybacks

Log in to collect

Academic paper

Share Buybacks and Abnormal Returns

AuthorsArne Uekoetter; Theodoros Evgeniou

Institute
  • ?INSEAD

Strategy in a nutshell

This strategy invests in U.S. stocks announcing buybacks within the past three months. The investor forms an equally weighted daily portfolio and hedges market exposure by shorting the iShares Russell 2000 ETF (IWM) using a rolling beta, capturing buyback-driven returns while managing market risk.

Economic rationale

Research shows that share repurchases signal undervaluation, allowing firms to profitably buy back shares. Returns are particularly strong when companies are widely held by retail investors, who may not fully recognize the value opportunity.

Backtest performance

Annualised return9.66%
Volatility7.21%
Beta0.014
Sharpe ratio1.34
Win rate58%

Full Python code

from QuantConnect.Data.Custom.SmartInsider import *
class MarketNeutralStrategyBasedShareBuybacks(QCAlgorithm):
def Initialize(self):
# SmartInsider data starts in 2015 for most stocks.
self.SetStartDate(2015, 1, 1)
self.SetCash(100000) 
data = self.AddEquity('IWM', Resolution.Daily)
data.SetLeverage(10)
self.symbol = data.Symbol

self.course_count = 500
self.last_course = []

self.period = 3 * 30
self.long = []

self.selection_flag = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)

# Weekly rebalance.
self.Schedule.On(self.DateRules.Every(DayOfWeek.Friday), self.TimeRules.BeforeMarketClose(self.symbol), self.Selection)
def OnSecuritiesChanged(self, changes):
for security in changes.AddedSecurities:
    security.SetFeeModel(CustomFeeModel(self))
    security.SetLeverage(10)
def CoarseSelectionFunction(self, coarse):
if not self.selection_flag:
    return Universe.Unchanged

selected = sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa' and x.Price > 5],
    key=lambda x: x.DollarVolume, reverse=True)

self.last_course = [x.Symbol for x in selected[:self.course_count]]

return self.last_course

def OnData(self, data):
if not self.selection_flag:
    return
self.selection_flag = False

for symbol in self.last_course:
    # Add smart insider data.
    smart_insider_symbol = self.AddData(SmartInsiderTransaction, symbol, Resolution.Daily).Symbol
    
    # NOTE:
    # v.1 - Iterate over last transactions. There's a weird "lag" between actual date and last buyback date. 
    # - faster, not so precise I guess.
    transactions = self.Securities[symbol].Data.GetAll(SmartInsiderTransaction)
    if any(x.BuybackDate >= self.Time - timedelta(days = self.period) for x in transactions):
        self.long.append(symbol)
        
    # v.2 Get buyback history.
    # - slow due to History() call, more precise.
    # history = self.History(SmartInsiderTransaction, smart_insider_symbol, 60, Resolution.Daily)
    # if not history.empty:
    #     self.long.append(symbol)

# Trade execution
count = len(self.long)
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in stocks_invested:
    if symbol not in self.long:
        self.Liquidate(symbol)
for symbol in self.long:
    self.SetHoldings(symbol, 1 / count)

# Hedge with IWM with no leverage.
self.SetHoldings(self.symbol, -1)
self.long.clear()

def Selection(self):
self.selection_flag = True

# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))