Original paper
Abstract
In this paper, we first document evidence of underreaction to management forecast news. We then hypothesize that the credibility of the forecast influences the magnitude of this underreaction. Relying on evidence that more credible forecasts are associated with a larger reaction in the short window around the management forecasts and a smaller post-management forecast drift in returns, we show that the magnitude of the underreaction is smaller for firms that provide more credible forecasts. Our paper contributes to the literature by providing out-of-sample evidence of the drift in returns documented in the post-earnings-announcement drift literature, with the credibility of the news being one explanation for the phenomenon.
Keywords:Â Market efficiency, Management forecasts, Earnings, Disclosure quality
Trading rules
- Investment universe: Firms from NYSE, AMEX, and NASDAQ.
- Criteria: Companies giving quarterly earnings predictions with stock value above $1.
- Forecast surprise: Management EPS forecast - Pre-management-forecast analyst consensus mean EPS forecast.
- Sorting: Quintile portfolios based on forecast surprise
- Trading strategy: Go long on firms with upward surprises and short on those with downward surprises.
- Start trade: Initiate on the third day after management forecast.
- Duration of trade: Hold positions for a quarter.
- Allocation strategy: Distribute equal weights to stocks in the portfolio.
Python code
Backtrader
import backtrader as bt
class PostGuidanceDrift(bt.Strategy):
params = (
('hold_period', 63),
('ranking_period', 1)
)
def __init__(self):
self.quarterly_earnings = dict()
self.forecast_surprise = dict()
def next(self):
if len(self) % self.params.ranking_period:
return
for d in self.datas:
if d._name not in self.quarterly_earnings:
continue
surprise = self.quarterly_earnings[d._name]['management_eps_forecast'] - self.quarterly_earnings[d._name]['pre_mgmt_analyst_consensus_mean']
self.forecast_surprise[d._name] = surprise
sorted_by_surprise = sorted(self.forecast_surprise.items(), key=lambda x: x[1], reverse=True)
quintile_size = len(sorted_by_surprise) // 5
long_stocks = [x[0] for x in sorted_by_surprise[:quintile_size] if x[1] > 0]
short_stocks = [x[0] for x in sorted_by_surprise[-quintile_size:] if x[1] < 0]
for d in self.datas:
if d._name in long_stocks:
self.order_target_percent(data=d, target=1 / len(long_stocks))
elif d._name in short_stocks:
self.order_target_percent(data=d, target=-1 / len(short_stocks))
else:
self.order_target_percent(data=d, target=0)
def notify_order(self, order):
pass
def notify_trade(self, trade):
pass
cerebro = bt.Cerebro()
# Add your data feeds here
cerebro.addstrategy(PostGuidanceDrift)
cerebro.run()
Please note that this code only provides the core structure of the strategy. You will need to provide the quarterly earnings data in the self.quarterly_earnings
dictionary with the required fields (management_eps_forecast
and pre_mgmt_analyst_consensus_mean
). You’ll also need to load your data feeds for NYSE, AMEX, and NASDAQ companies and ensure they have the required quarterly earnings guidance and stock prices greater than $1.