Original paper
Abstract
This paper provides evidence that insiders possess, and trade upon, knowledge of specific and economically-significant forthcoming accounting disclosures as long as two years prior to the disclosure. Stock sales by insiders increase three to nine quarters prior to a break in a string of consecutive increases in quarterly earnings. Insider stock sales are greater for growth firms, before a longer period of declining earnings, and when the earnings decline at the break is greater. Consistent with avoiding an established legal jeopardy, there is little abnormal selling in the two quarters immediately prior to the break.
Keywords:Â Insider trading, Securities regulation
Trading rules
- Investment universe: NYSE, AMEX, NASDAQ stocks over $2 (excluding closed-end funds, REITs, 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)
- Hold portfolio for one year, then rebalance
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.