Event-Driven and Arbitrage Strategies › Earnings Event Strategies

Michael BrenndoerferJanuary 6, 20268 min read
Quantitative FinanceAll chapters
Part 11 of 15Quantitative Trading Strategies
Chapter 64Event-Driven and Arbitrage Strategies › Earnings Event Strategies
Reading section 5 of 11 · Earnings Event Strategies · 8 minSection 5 of 11 · 8 min

Book contents

Section 5 of 11 · 8 min read

Event-Driven and Arbitrage Strategies › Earnings Event StrategiesOne section · Sections in this chapter5 / 11 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Earnings announcements represent the most frequent and liquid corporate events. Companies report quarterly, creating a regular calendar of volatility events. The predictable timing combined with substantial price moves makes earnings a natural target for systematic trading.

Earnings Surprise and Price Reaction

The relationship between earnings surprises and stock returns has been studied extensively. The post-earnings announcement drift (PEAD), introduced by Ball and Brown (1968), documents that stocks with positive earnings surprises tend to continue outperforming for weeks after the announcement, while stocks with negative surprises continue underperforming. PEAD has persisted across decades and markets despite extensive academic documentation.

To quantify the surprise, analysts often use Standardized Unexpected Earnings (SUE). This metric acts as a signal-to-noise ratio, scaling the raw earnings beat by the historical volatility of forecast errors to distinguish meaningful surprises from normal variation. Standardization makes earnings surprises comparable because a $0.05 beat means something very different for a stable utility company than for a volatile technology firm:

SUE=EPSactual−EPSexpectedσsurprise\text{SUE} = \frac{\text{EPS}_{\text{actual}} - \text{EPS}_{\text{expected}}}{\sigma_{\text{surprise}}}

where:

  • SUE\text{SUE}: standardized unexpected earnings
  • EPSactual\text{EPS}_{\text{actual}}: reported earnings per share
  • EPSexpected\text{EPS}_{\text{expected}}: consensus analyst estimate
  • σsurprise\sigma_{\text{surprise}}: standard deviation of historical forecast errors

The numerator measures the raw surprise in dollars per share, while the denominator normalizes this surprise by the typical magnitude of forecast errors for that company. A SUE of 2.0 indicates a positive surprise equal to twice the historical standard deviation of analyst forecast errors for that company.

In[17]:
Code
import numpy as np
import pandas as pd


def calculate_standardized_surprise(
    actual_eps, expected_eps, historical_surprise_std
):
    """
    Calculate standardized unexpected earnings (SUE).

    SUE normalizes the surprise by historical surprise volatility,
    making surprises comparable across stocks.
    """
    surprise = actual_eps - expected_eps
    sue = (
        surprise / historical_surprise_std if historical_surprise_std > 0 else 0
    )
    return sue


# Generate synthetic earnings data
np.random.seed(123)
n_events = 500

# Simulate earnings surprises and subsequent returns
sue_scores = np.random.normal(0, 1, n_events)
# PEAD effect: positive surprises lead to positive drift
announcement_returns = 0.02 * sue_scores + np.random.normal(0, 0.05, n_events)
drift_returns = 0.005 * sue_scores + np.random.normal(
    0, 0.03, n_events
)  # next 20 days
total_returns = announcement_returns + drift_returns

# Analyze returns by SUE quintile
earnings_df = pd.DataFrame(
    {
        "sue": sue_scores,
        "announcement_return": announcement_returns,
        "drift_return": drift_returns,
        "total_return": total_returns,
    }
)

earnings_df["sue_quintile"] = pd.qcut(
    earnings_df["sue"], 5, labels=["Q1 (Worst)", "Q2", "Q3", "Q4", "Q5 (Best)"]
)

quintile_analysis = (
    earnings_df.groupby("sue_quintile")
    .agg(
        {
            "sue": "mean",
            "announcement_return": "mean",
            "drift_return": "mean",
            "total_return": "mean",
        }
    )
    .round(4)
)
Out[18]:
Visualization
Grouped bar chart showing average announcement return and drift return for five earnings surprise quintiles from Q1 worst to Q5 best, showing a monotonic increase in both return components as the earnings surprise improves.
Post-earnings announcement returns and subsequent 20-day drift increase monotonically across the simulated earnings-surprise quintiles. Q5 averages a 2.50% announcement return plus 0.50% drift, while Q1 averages a 3.86% announcement loss plus a further 0.41% decline.
Out[19]:
Console
Returns by Earnings Surprise Quintile
============================================================
                 sue  announcement_return  drift_return  total_return
sue_quintile                                                         
Q1 (Worst)   -1.4416              -0.0386       -0.0041       -0.0426
Q2           -0.5695              -0.0067        0.0019       -0.0048
Q3           -0.0480              -0.0008        0.0022        0.0014
Q4            0.4846               0.0071        0.0029        0.0099
Q5 (Best)     1.3813               0.0250        0.0050        0.0301

The analysis confirms the PEAD phenomenon: stocks in the highest SUE quintile (Q5) show the strongest immediate announcement returns and continue to drift upward, generating positive post-announcement returns. Conversely, the lowest quintile (Q1) exhibits negative drift, with both the announcement return and the subsequent drift moving downward in tandem.

Earnings Trading Approaches

Several systematic approaches exploit earnings events:

Pre-earnings momentum: Stocks that have exhibited strong momentum leading into earnings tend to have positive surprises. This reflects the market partially anticipating earnings outcomes but leaving room for continued repricing upon actual announcement. A strategy might go long stocks with strong pre-earnings momentum and short those with weak momentum.

Volatility trading around earnings: As we explored in the volatility trading chapter, implied volatility rises before earnings and collapses afterward. Strategies can sell straddles before earnings to collect the higher premiums or buy straddles when implied volatility appears compressed relative to typical realized moves for that security.

PEAD capture: Trading in the direction of the surprise after the announcement captures the drift. The challenge is speed: announcement returns happen in seconds, while the drift unfolds over days to weeks.

In[20]:
Code
def earnings_straddle_analysis(
    stock_price,
    iv_pre_earnings,
    realized_move_pct,
    days_to_earnings,
    risk_free_rate=0.05,
):
    """
    Analyze a short straddle strategy around earnings.

    Compares implied volatility premium collected vs. realized move.
    """
    # Convert IV to expected move over the period
    T = days_to_earnings / 365
    expected_move_implied = stock_price * iv_pre_earnings * np.sqrt(T)

    # Simple ATM straddle pricing (using Black-Scholes approximation)
    # For ATM options: Call ≈ Put ≈ 0.4 * S * σ * sqrt(T)
    call_price = 0.4 * stock_price * iv_pre_earnings * np.sqrt(T)
    put_price = call_price  # ATM approximation
    straddle_price = call_price + put_price

    # Breakeven move
    breakeven_move_pct = straddle_price / stock_price

    # P&L from short straddle given realized move
    realized_move_dollar = stock_price * abs(realized_move_pct)
    straddle_pnl = straddle_price - realized_move_dollar
    straddle_return = straddle_pnl / straddle_price

    return {
        "straddle_price": straddle_price,
        "straddle_pct_of_stock": straddle_price / stock_price,
        "implied_move": expected_move_implied,
        "implied_move_pct": expected_move_implied / stock_price,
        "breakeven_move_pct": breakeven_move_pct,
        "realized_move_pct": abs(realized_move_pct),
        "short_straddle_pnl": straddle_pnl,
        "short_straddle_return": straddle_return,
        "stock_price": stock_price,
        "iv": iv_pre_earnings,
        "days": days_to_earnings,
    }


# Example: Stock at $100, IV 80% (elevated for earnings), actual move 5%
straddle_result = earnings_straddle_analysis(
    stock_price=100,
    iv_pre_earnings=0.80,  # Annualized IV
    realized_move_pct=0.05,
    days_to_earnings=2,
)
Out[21]:
Console
Earnings Straddle Analysis
=============================================
Stock Price: $100.00
Pre-Earnings IV: 80%
Days to Earnings: 2

Straddle Pricing:
  Straddle Cost: $4.74
  As % of Stock: 4.7%
  Implied Move: 5.9%
  Breakeven Move: 4.7%

Realized Outcome:
  Actual Move: 5.0%
  Short Straddle P&L: $-0.26
  Short Straddle Return: -5.5%

The short straddle profits because the realized move of 5% was contained within the breakeven range implied by the option premiums. The pre-earnings implied volatility of 80% priced in a 6.9% move. Since the actual reaction was milder, the volatility risk premium was successfully harvested.

Analyst Revisions and Other Information Events

Beyond scheduled earnings, other information events create trading opportunities:

Analyst estimate revisions: When analysts revise their earnings estimates, stocks often drift in the direction of the revision. The revision captures private information that isn't yet fully reflected in prices.

Index reconstitutions: When indexes like the S&P 500 add or remove stocks, passive funds must buy or sell. This predictable demand creates price pressure that can be traded ahead of the effective date. Research shows stocks added to major indexes experience positive abnormal returns, while deleted stocks suffer negative returns.

Credit rating changes: Rating upgrades and downgrades affect bond prices directly but also signal information about equity value. The impact is asymmetric: downgrades tend to have larger effects than upgrades.

In[22]:
Code
# Simulate index reconstitution effect
np.random.seed(456)

# Event window: -20 to +20 days around announcement
event_window = np.arange(-20, 21)

# Addition effect: price rises as announcement approaches, peaks at effective date
# Reversion afterward
addition_car = np.zeros(len(event_window))
for i, day in enumerate(event_window):
    if day <= 0:
        addition_car[i] = 0.03 * (1 - np.exp(-0.1 * (day + 20)))  # gradual rise
    else:
        addition_car[i] = 0.03 - 0.01 * (
            1 - np.exp(-0.05 * day)
        )  # partial reversion

# Deletion effect: opposite pattern
deletion_car = -addition_car * 0.8  # slightly smaller magnitude

# Add noise
addition_car += np.random.normal(0, 0.002, len(event_window)).cumsum() * 0.3
deletion_car += np.random.normal(0, 0.002, len(event_window)).cumsum() * 0.3
Out[23]:
Visualization
Line chart of cumulative abnormal return from day -20 to +20 around an index reconstitution. Additions climb to roughly 3% near day 0 before partially reverting, while deletions follow a smaller negative mirror pattern.
Simulated cumulative abnormal returns diverge ahead of an index reconstitution. Additions rise toward 3% by the effective date, briefly peak just afterward, and ease to about 2.6% by day +20; deletions trace a smaller negative pattern and partially recover after the event.