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
- 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.
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, reverse=True) quintile_size = len(sorted_by_surprise) // 5 long_stocks = [x for x in sorted_by_surprise[:quintile_size] if x > 0] short_stocks = [x for x in sorted_by_surprise[-quintile_size:] if x < 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 (
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.