Backtest Period
1987-2004
Markets Traded
Equities
Maximum Drawdown
Period of Rebalancing
Quarterly
Return (Annual)
15%
Sharpe Ratio
Standard Deviation (Annual)
Original paper
SSRN-id1087391.pdf169.6KB
Trading rules
- Investment universe: All NYSE, AMEX, and NASDAQ stocks (excluding financial and utility firms and stocks below $5)
- Factor 1: EAR (Earnings Announcement Return), calculated as the abnormal return during a 3-day window around the announcement date, in excess of a similar risk portfolio
- Factor 2: SUE (Standardized Unexpected Earnings), calculated as the earnings surprise divided by the standard deviation of earnings surprises
- Sorting: Stocks sorted into quintiles based on EAR and SUE, using previous quarter data to prevent look-ahead bias
- Portfolio weighting: Equal weight for each stock within quintiles
- Long/short positions: Go long on top SUE and EAR quintile intersection, short on bottom SUE and EAR quintile intersection
- Holding period: 1 quarter (60 working days) from the second day after the earnings announcement
- Rebalancing: Quarterly
Python code
Backtrader
import backtrader as bt
class PEADStrategy(bt.Strategy):
def __init__(self):
self.ear = {} # Earnings Announcement Return
self.sue = {} # Standardized Unexpected Earnings
def next(self):
# Check if it's the second day after the earnings announcement
if not self.is_second_day_after_announcement():
return
# Calculate factors EAR and SUE for each stock
self.calculate_factors()
# Sort stocks into quintiles based on EAR and SUE
long_stocks, short_stocks = self.sort_quintiles()
# Calculate equal weight for each stock within quintiles
long_weight = 1.0 / len(long_stocks)
short_weight = -1.0 / len(short_stocks)
# Enter long and short positions
for stock in long_stocks:
self.order_target_percent(stock, target=long_weight)
for stock in short_stocks:
self.order_target_percent(stock, target=short_weight)
# Hold positions for 1 quarter (60 working days)
self.hold_positions_for_n_days(60)
def is_second_day_after_announcement(self):
# Implement logic to determine if it's the second day after the earnings announcement
pass
def calculate_factors(self):
# Implement logic to calculate EAR and SUE for each stock in the investment universe
pass
def sort_quintiles(self):
# Implement logic to sort stocks into quintiles based on EAR and SUE
pass
def hold_positions_for_n_days(self, n):
# Implement logic to hold positions for n days
pass
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Add data feeds for NYSE, AMEX, and NASDAQ stocks (excluding financial and utility firms and stocks below $5)
cerebro.addstrategy(PEADStrategy)
cerebro.run()
Please note that this is just a skeleton code for the PEAD strategy. You need to implement the logic for each function based on your data source and the desired calculations.