Original paper
Abstract
Both short-term momentum and long-term reversal are attributable to investors underreacting to preceding insider trading information. Past winners (losers) continue to earn significant positive (negative) returns in the short term only if their insider trading activity indicates positive (negative) information. Thus, short-term momentum is attributable to investors underreacting to insider information that confirms past return. In the long term, past winners (losers) earn significant negative (positive) returns only if their insider trading activity indicates negative (positive) information. Thus, long-term reversal is attributable to investors underreacting to insider information that disconfirms past return. After controlling for insider trading information, there is no evidence of overreaction. Further, there is a clear "division of labor" between stocks that contribute to momentum and stocks that contribute to reversal.
Keywords:Â Momentum, Reversal, Insider trading, Insider silence, Underreaction, Overreaction
Trading rules
- Target stocks: Stocks listed on NYSE, AMEX, and NASDAQ (but exclude those priced under $5, those in the lowest NYSE market cap decile, and those with either absent or non-positive equity book value)
- Monthly, determine net insider demand (NID) for each stock monthly: (Total insider shares bought - Total insider shares sold) / Total shares outstanding at month-end
- Organize portfolios influenced by half-year return history and insider transaction patterns:
- Winner: Top decile stocks based on past six-month returns
- Loser: Bottom decile stocks based on past six-month returns
- Silence: Stocks with no insider trading activity in the past six months
- Traded: Stocks with any insider trading activity in the past six months
- Buy: Stocks with positive NID
- Sell: Stocks with non-positive NID
- On a monthly basis:
- Initiate long positions in Winner stocks indicating positive NID
- Initiate short positions in Loser stocks from the Inactive group
- Hold selected stocks for a 12-month period (adjusting 1/12 of the portfolio every month)
- Assign identical weights to each stock in the portfolio.
Python code
Backtrader
import backtrader as bt
class InsiderMomentumStrategy(bt.Strategy):
params = dict(
lookback=126, # 6-month lookback for momentum
hold_period=252, # 12-month holding period
top_decile=0.1,
bottom_decile=0.1,
)
def __init__(self):
self.insider_data = {}
self.stock_returns = {}
self.orders = {}
def next(self):
if len(self.data) < self.p.lookback:
return
if self.data.datetime.date(0).month != self.data.datetime.date(-1).month:
self.update_insider_data()
self.rank_stocks()
self.rebalance_portfolio()
def update_insider_data(self):
for d in self.datas:
self.insider_data[d._name] = self.get_net_insider_demand(d)
def get_net_insider_demand(self, data):
# Calculate net insider demand for each stock
return (data.insider_buys - data.insider_sells) / data.shares_outstanding
def rank_stocks(self):
self.stock_returns = {d._name: d.close[0] / d.close[-self.p.lookback] for d in self.datas}
sorted_returns = sorted(self.stock_returns.items(), key=lambda x: x[1], reverse=True)
decile_size = int(len(sorted_returns) * self.p.top_decile)
self.winners = {k for k, _ in sorted_returns[:decile_size]}
self.losers = {k for k, _ in sorted_returns[-decile_size:]}
self.silence = {k for k, v in self.insider_data.items() if v == 0}
self.traded_buy = {k for k, v in self.insider_data.items() if k in self.winners and v > 0}
self.traded_sell = {k for k, v in self.insider_data.items() if k in self.losers and v <= 0}
def rebalance_portfolio(self):
positions = [d._name for d, pos in self.positions.items() if pos]
for d in self.datas:
stock = d._name
if stock in positions:
if stock in self.traded_sell or stock in self.silence:
self.close(data=d)
continue
if stock in self.traded_buy:
target_weight = 1.0 / len(self.traded_buy)
self.order_target_percent(data=d, target=target_weight)
elif stock in self.losers and stock in self.silence:
target_weight = -1.0 / len(self.losers)
self.order_target_percent(data=d, target=target_weight)
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Add data feeds to cerebro here
cerebro.addstrategy(InsiderMomentumStrategy)
cerebro.run()
Please note that this code assumes the data feed provided contains the necessary fields for insider trading activity (insider_buys, insider_sells, and shares_outstanding). The data feed should be prepared with the relevant data before using it with this strategy.