Mk.
Personal research project

After the Earnings

A personal research project on market surprise.

Whole-portfolio CAGR20.9%SPY benchmark 14.8%
Annualised portfolio alpha≈ 3.0%Whole-portfolio measure
Maximum drawdown36.7%Historical simulation
Entries2272013–2026

CAGR includes a SPY core targeting 100% of NAV plus additional signal stocks, which may use margin. It is not the signal-only return. SPY tracks the S&P 500; the portfolio does not hold the SPX index directly.

Final-run summary from the supplied version comparison; curves and annual returns from the supplied CSVs. The strategy has not been independently rerun.

  • Python
  • pandas
  • QuantConnect / LEAN
  • yfinance
  • Nasdaq API

Starting point: expectations and surprises

Before an earnings release, option prices reflect how much the market expects the stock to move. I used pre-announcement at-the-money straddles to estimate that move, then compared it with the stock's actual reaction:

shock = actual reaction ÷ expected move

My initial hypothesis was that a larger shock might mean the market had underestimated the earnings news, making the stock more likely to keep moving in the same direction. This ratio is a simplified description; the QC implementation is explained in the method notes.

  1. Before release: options-implied move
  2. Earnings release and actual reaction
  3. Actual move ÷ expected = shock
  4. Next 63 trading days

Finding 1: shock alone told me little

I compared S&P 500 earnings events from 2013 to 2026, grouping positive reactions of at least 2% by shock size. I looked at the next 63 trading days' excess return vs other stocks reporting that month, after costs.

Shock Average subsequent excess return
1.25–1.5 +0.3%
1.5–2 +0.1%
2–3 +0.4%
Above 3 +1.3% — small sample, not significant
Returns by shock bucket
Returns by shock bucketAverage 63-trading-day excess return vs other stocks reporting that month, after costs. 1.25–1.5: +0.3%; 1.5–2: +0.1%; 2–3: +0.4%; 3+: +1.3%. Grey: the 3+ bucket has a small sample and is not significant.0%0.5%1%1.5%+0.3%1.25–1.5+0.1%1.5–2+0.4%2–3+1.3%3+

Average 63-trading-day excess return vs other stocks reporting that month, after costs. Grey: the 3+ bucket has a small sample and is not significant.

Larger shocks did not produce a consistent improvement. Knowing how far the reaction exceeded expectations was not enough to distinguish which stocks would keep rising.

Finding 2: the actual price reaction

I then focused on the size of the actual gain. Keeping shock ≥ 1.25, I grouped events by how much the stock had risen that day:

Actual reaction-day gain Average subsequent excess return
2–5% −0.3%
5–10% +0.1%
10–15% +1.2% — only group to pass the predefined criteria
Above 15% +2.0% — variable results, not significant
Returns by actual gain
Returns by actual gainAverage 63-trading-day excess return vs other stocks reporting that month, after costs. 2–5%: −0.3%; 5–10%: +0.1%; 10–15%: +1.2%; 15%+: +2.0%. Outlined: 10–15% is the only bucket that passed the predefined criteria. Grey: above 15% is variable and not significant.-0.5%0%0.5%1%1.5%2%2.5%−0.3%2–5%+0.1%5–10%+1.2%10–15%+2.0%15%+

Average 63-trading-day excess return vs other stocks reporting that month, after costs. Outlined: 10–15% is the only bucket that passed the predefined criteria. Grey: above 15% is variable and not significant.

The smaller gains showed little continuation. In this screening exercise, only the 10–15% group passed my predefined criteria, with positive results in both 2013–19 and 2020–26.

Raising the shock threshold to 2 did not improve the result. In these tests, the actual price reaction appeared more useful than a larger shock ratio alone.

Turning it into a strategy

The resulting rule focused on the actual gain. I retained shock ≥ 1.25 as a basic filter for reactions exceeding the options-based estimate, rather than treating a higher shock as sufficient evidence on its own.

  • Large caps: top 250 S&P 500 stocks by weight, according to the reported final code setting.
  • Reaction-day gain ≥ 10% and shock ≥ 1.25.
  • Buy at the next open, targeting 15% per name, with at most 10 names and a separate signal-exposure cap.
  • Exit at the close of the 21st trading day, counting entry as day one.
  • Target a SPY core equal to 100% of NAV, with additional signal stocks on top. Total stock exposure can exceed NAV and involve margin financing.
Final run: portfolio and SPY benchmark

━ Strategy portfolio┄ Exported SPY benchmark

Final run: portfolio and SPY benchmarkMonthly portfolio and exported benchmark series, each rebased to 100, from 2013 to June 2026. Supplied historical-simulation CSV, not live returns.050010001500201320202026

Start = 100 · Monthly observations · 2026 through June · Portfolio includes SPY and signal positions.

The 20.9% CAGR is the return of this entire portfolio, including the SPY core and additional signal positions. SPY tracks the S&P 500; it is the holding used here, rather than the SPX index itself. The reported beta is about 1.19 and annualised alpha about 3.0%. Alpha is also a whole-portfolio measure and does not isolate the earnings signal's contribution.

Annual returns

━ Strategy portfolio┄ Exported SPY benchmark

Annual returns2013: Strategy portfolio +29.4%, SPY benchmark +31.6%; 2014: Strategy portfolio +18.4%, SPY benchmark +15.2%; 2015: Strategy portfolio +6.3%, SPY benchmark +1.3%; 2016: Strategy portfolio +18.3%, SPY benchmark +10.9%; 2017: Strategy portfolio +30.0%, SPY benchmark +21.7%; 2018: Strategy portfolio −7.1%, SPY benchmark −5.4%; 2019: Strategy portfolio +46.7%, SPY benchmark +32.1%; 2020: Strategy portfolio +38.0%, SPY benchmark +18.0%; 2021: Strategy portfolio +21.9%, SPY benchmark +29.7%; 2022: Strategy portfolio −18.6%, SPY benchmark −18.4%; 2023: Strategy portfolio +42.1%, SPY benchmark +26.2%; 2024: Strategy portfolio +55.3%, SPY benchmark +25.3%; 2025: Strategy portfolio +17.7%, SPY benchmark +18.2%; 2026 YTD: Strategy portfolio +7.1%, SPY benchmark +6.7%-20%0%20%40%60%20132014201520162017201820192020202120222023202420252026 YTD

2026 YTD runs through June only, not a full year. Exact values are in the table below.

YearStrategy portfolioSPY benchmark
2013+29.4%+31.6%
2014+18.4%+15.2%
2015+6.3%+1.3%
2016+18.3%+10.9%
2017+30.0%+21.7%
2018−7.1%−5.4%
2019+46.7%+32.1%
2020+38.0%+18.0%
2021+21.9%+29.7%
2022−18.6%−18.4%
2023+42.1%+26.2%
2024+55.3%+25.3%
2025+17.7%+18.2%
2026 YTD+7.1%+6.7%

Versions tested

The supplied version comparison records changes in universe, position size and start date. The final version is the last row. These are different historical simulations, not a controlled comparison of any single change. Reported SPY benchmark CAGR is 14.8% for the 2013 start and 14.3% for the 2018 start.

Version Period Universe Per name CAGR Alpha Max drawdown
A 2013–2026 Top 150 10% 17.2% 1.2% 34.5%
B 2018–2026 Top 250* 10% 19.6% 2.9% 36.2%
C 2018–2026 Top 250* 15% 21.3% 3.9% 36.7%
Final 2013–2026 Top 250* 15% 20.9% 3.0% 36.7%

*Top 250 is the setting described by the supplied code-comparison report. The later runs' exact universe has not been confirmed by a matching run log. A's top-150 setting is log-backed. All periods end in June 2026.

What did not work

The saved research records include rejected ideas as well as the rule I kept.

Holding winners longer

I wrote down three pass criteria before testing. One setting raised out-of-sample Sharpe from 0.63 to 0.88, but only three of nine nearby settings beat the original rule. The main rule also failed the combined criteria, so I did not adopt it.

Shorting large negative reactions

In the saved output, the short-only portfolio had a negative Sharpe in 2020–26. I retained the long side only.

Fiscal-first-quarter effects

Following the research direction in Wang & Zeng (2026), I tested whether earnings drift was stronger in the first fiscal quarter. My 2020–26 test did not meet the predefined criteria, so I did not add it to the rules.

A data-setting mistake

The dividend-adjustment setting in an early version distorted percentage returns for some stocks. My research notes record that I rebuilt the study after finding it.

Lessons

  1. A data-setting mistake can invalidate every conclusion built on it.
  2. Write the criteria before looking at the results.
  3. Understand where the apparent edge comes from. The research suggested dependence on a few large winners, so I deliberately left take-profit orders out of the rule.

Now

Backtests describe the past, so I built a small Python paper-trading tool to take the research into forward tracking.

Tracking began on 24 September 2026. The supplied records through 22 October contain one signal position and no completed trades. It needs more time before performance can be assessed.

The scanner uses a simplified, single-expiry straddle estimate. Its expected-move and shock calculations differ from QC V2, so it is not an exact replica of the backtest.

For now, this is a small paper-trading project in progress. I use it to record signals and virtual positions, and watch how they develop as new data comes in.

Data and method notes

The screening tables and bars show rounded, cost-adjusted average 63-trading-day excess return vs other stocks reporting that month. The figures survive in the research chat's saved output. The notebook contains the test code but no saved execution outputs, and its research cache is missing. These tests have not been independently rerun; company-size and mid-cap result tables remain unavailable.

The final-run curve and annual returns are generated at build time from the supplied CSVs attributed to the final QuantConnect export. Their yearly changes reconcile within the supplied rounding. This checks consistency between the files, not the underlying strategy. The original final-run JSON has not been inspected here; headline and version metrics come from the supplied brief and comparison report. Source A/B exports were inspected earlier. The baseline date in the monthly CSV is 1 January 2013; its final point is 27 June 2026 UTC, the export boundary for the June end date. The exported benchmark's full configuration has not been independently reconstructed.

The opening shock ratio is schematic. QC V2 estimates event variance from implied volatility at two expiries and uses ln(1 + actual return) / expected move. The scanner uses a single-expiry straddle divided by spot and actual / expected move. Recorded option volume, open interest and bid–ask spreads were not used as threshold filters. Quote timing, especially around after-close announcements, still needs validation; the supplied tracking version lacks the later reaction-session-open snapshot guard.

QC research uses EODHD earnings dates, historical SPY constituent weights and QC price/option histories. The scanner uses Nasdaq and yfinance. It models financing but not fees or slippage. Paper records are observations, not verified trading performance.

Monthly equity CSV · Annual returns CSV

Research note

Historical simulation, not live trading. Personal research, not investment advice. Most of the writing was produced with AI assistance.

Comments

Comments are not open yet.

— Mk