Backtest Period
1975-2013
Markets Traded
Equities
Maximum Drawdown
Period of Rebalancing
Daily
Return (Annual)
41%
Sharpe Ratio
Standard Deviation (Annual)
Original paper
SSRN-id2460166.pdf1004.3KB
Trading rules
- Investment universe: NYSE, AMEX, and NASDAQ-listed common stocks
- Exclude stocks: Price <$5, missing market cap, or missing earnings data for any of the past 20 quarters
- Predicted seasonality measure (earnrank): Utilize five years of earnings data (quarters t-23 to t-4)
- Compute firm earnings per share (adjusted for stock splits)
- Rank 20 quarters of earnings data (largest to smallest)
- Calculate earnrank for quarter t: average rank of quarters t-4, t-8, t-12, t-16, and t-20
- Go long: Top 5% of firms with highest earnrank
- Go short: Bottom 5% of firms with lowest earnrank
- Seasonality condition: Each firm appears in each portfolio only once per year
- Portfolio formation: At least 10 firms in both long and short positions
- Trading timeline: Open positions on day t-1, hold until day t+1 (day t is the announcement day)
- Portfolio management: Equally weighted, daily rebalancing
Python code
Backtrader
import backtrader as bt
class EarningsSeasonalityStrategy(bt.Strategy):
def __init__(self):
self.earnings_data = self.get_earnings_data()
def get_earnings_data(self):
# Load and preprocess earnings data here
pass
def compute_earnrank(self, data):
# Compute earnrank for given stock data
pass
def next(self):
long_stocks = []
short_stocks = []
for data in self.datas:
if data.close[0] < 5 or data.missing_data:
continue
earnrank = self.compute_earnrank(data)
if earnrank is None:
continue
if earnrank >= 0.95:
long_stocks.append(data)
elif earnrank <= 0.05:
short_stocks.append(data)
if len(long_stocks) >= 10 and len(short_stocks) >= 10:
long_weight = 1.0 / len(long_stocks)
short_weight = -1.0 / len(short_stocks)
for data in long_stocks:
self.order_target_percent(data, target=long_weight)
for data in short_stocks:
self.order_target_percent(data, target=short_weight)
else:
self.close_positions()
def close_positions(self):
for data in self.datas:
self.order_target_percent(data, target=0)
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Add data feeds to cerebro here
cerebro.addstrategy(EarningsSeasonalityStrategy)
cerebro.run()
Please note that this code is a starting point and some parts of the code like get_earnings_data
and compute_earnrank
should be implemented according to your specific data sources and requirements. You would also need to add your data feeds to the cerebro
instance.