Quant Buffet放轻松,别过度思虑

基于股票回购的市场中性策略

登录后收藏

学术论文

Share Buybacks and Abnormal Returns

作者股票回购与异常回报 [点击查看论文]

机构
  • ?INSEAD

策略概要

该策略针对在纽约证券交易所、纳斯达克和美国证券交易所上市的股票,市值超过2亿美元,股价超过5美元。每天,投资者形成一个等权重的投资组合,其中包括在过去三个月内宣布股票回购的公司。为了对冲该投资组合,投资者根据投资组合层面的贝塔值做空iShares Russell 2000 ETF (IWM),贝塔值使用最近250天的滚动窗口计算。该策略旨在利用股票回购公告获利,同时通过使用IWM的动态对冲来管理市场风险。

II. 策略合理性

学术研究表明,股票回购与公司目前被低估的预期一致。有强有力的证据表明,公司能够盈利性地回购股票,特别是当公司被大量不成熟的散户投资者持有的时候。

回测表现

波动率7.21%
夏普比率1.34
胜率58%

完整 Python 代码

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