Original paper
Abstract
This study examines the dynamics between insider trades and analyst recommendations and evaluates the joint and relative informativeness of these two activities. We show that analyst recommendations significantly affect subsequent insider trading, but not vice versa. Surprisingly, insiders in aggregate purchase more shares following analyst downgrades and sell more shares following upgrades. This pattern persists even after controlling for insiders' contrarian trading tendency of buying shares after price declines and selling shares after price rises. Analysts, in contrast, do not materially take into account insider trading when revising their recommendations. Further, the return analysis shows that both insider trading and analyst recommendation revisions continue to provide valuable information about future returns even in the presence of the other group's activity. More importantly, the informativeness of insider trading is independent of analysts' activity whereas analyst recommendations are informative only when insiders exhibit more sales than buys or when they do not trade. Overall, our findings highlight the important dynamics and financial market consequences between the two groups of information providers.
Keywords:Â insider trading; analyst recommendations; information; return predictability
Trading rules
- Focus on stocks from NYSE, AMEX, and NASDAQ priced above $2 (but not closed-end funds, REITs, or ADRs)
- Calculate Net Purchase Ratio (NPR) for each stock over prior 6 months at end of April
- NPR = (Total insider purchases - Total insider sells) / Total transactions
- Sort stocks into deciles based on NPR
- Go long on top decile (highest NPR) and short on bottom decile (lowest NPR)
- Maintain the selected stocks for 12 months before making adjustments.
Python code
Backtrader
import backtrader as bt
import pandas as pd
import requests
class InsiderStrategy(bt.Strategy):
def __init__(self):
self.insider_data = None
def next(self):
if self.datetime.date().month == 4 and self.datetime.date().day == 30:
self.rebalance()
def rebalance(self):
self.rank_stocks()
long_stocks = self.insider_data.nlargest(10, 'NPR')
short_stocks = self.insider_data.nsmallest(10, 'NPR')
for stock in long_stocks['symbol']:
data = self.getdatabyname(stock)
size = self.broker.get_cash() * 0.1 / data.close[0]
self.buy(data=data, size=size)
for stock in short_stocks['symbol']:
data = self.getdatabyname(stock)
size = self.broker.get_cash() * 0.1 / data.close[0]
self.sell(data=data, size=size)
def rank_stocks(self):
self.insider_data = self.get_insider_data()
self.insider_data['NPR'] = (self.insider_data['purchases'] - self.insider_data['sells']) / self.insider_data['transactions']
self.insider_data.sort_values(by='NPR', ascending=False, inplace=True)
def get_insider_data(self):
# Implement data fetching from external sources and pre-processing as needed
pass
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Implement data loading and filtering based on the specified criteria
# Replace 'filtered_data' with the actual data source
for symbol, data in filtered_data.items():
cerebro.adddata(data, name=symbol)
cerebro.addstrategy(InsiderStrategy)
cerebro.broker.set_cash(100000)
cerebro.broker.setcommission(commission=0.001)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
results = cerebro.run()
final_value = results[0].broker.get_value()
print(f'Final Portfolio Value: {final_value}')
print('Sharpe Ratio:', results[0].analyzers.sharpe.get_analysis()['sharperatio'])
Note: This code is a template for the given strategy. You will need to implement the get_insider_data
function to fetch the insider trading data from an external source and preprocess it to calculate the NPR. Additionally, you will need to load and filter the stock data based on the specified criteria, and replace filtered_data
with the actual data source.