A point-in-time, no-lookahead event study across the S&P 500, with costs included, out-of-sample, and against an honest null. Try the two grounded tools first, then see what the study found.
Search any of the 480 S&P 500 companies, pick one of its 10-K filings, and a server-side model (GLM 5.2, free via NVIDIA) writes a short memo from the study's pre-computed numbers only. It never computes a figure and never gives advice. It can narrate only real, measured filing events, so it can't be pushed into predicting or inventing anything.
Loading companies…
Ask a plain-English question about a company's most recent 10-K risk-factor changes. The model answers only from the actual added, removed, and reworded sentences the study diffs out of that filing, with the source cited. If the answer isn't in those changes, it says so. Descriptive only: it won't predict, value, or advise.
The question. Every US public company files a yearly report with the SEC (a "10-K") that includes a "Risk Factors" section listing what could go wrong for the business. Companies reword that section a little every year. We test whether large changes in that language predict how the stock moves over the following weeks. There is a known finding (nicknamed "Lazy Prices") that the changes companies do make tend to carry real news, often bad news, that the market is slow to price in.
How we keep it honest. Four rules do the heavy lifting, and they are the real point of the project:
What we found. The effect is real but weak, and it does not survive trading costs. That honest "no" is the valuable result: a system you can trust to tell you when something does not hold up matters more than a flashy signal that falls apart in practice.
Free public data goes in one end; a calibrated verdict and two grounded tools come out the other. The research pipeline runs offline and is fully reproducible from the repo; the serving layer runs on Vercel.
Each filing's acceptance instant maps to the first market open strictly after it. No feature may use data from after that instant, enforced in code and checked by a pre-event placebo window.
Pull Item 1A (Risk Factors) out of the filing HTML, then sentence-diff it against last year's: added, removed, reworded. The diff grounds everything downstream; a model can label a change it is shown, but it can never invent one.
One row per filing event: net added risk language, document similarity, tone deltas. Every feature is computable from information available at the event instant, by construction.
Abnormal returns against three expected-return models (market, sector, market-model beta), CAR windows of 1, 5, and 21 days, plus the placebo window and momentum and beta controls.
Cross-sections with filing-date-clustered errors, an in-sample/holdout split examined once, and a net-of-cost long-short spread. Output: the verdict at the top of this page.
Serving layer. The pipeline's outputs are baked into the deployment as fixed, server-side data. The browser only ever sends an event ID or a question:
A single static file on Vercel. The company search reads a small pre-built index; no framework, no build step.
Looks up one real event's pre-computed numbers server-side and turns them into a cited memo.
Answers a question strictly from the real added, removed, and reworded sentences of one filing.
Free via NVIDIA's endpoint. The model restates and cites what the pipeline computed. It never computes a figure, never predicts, never advises. Unknown IDs are rejected before any model call; requests are rate-limited and cached.
Mean 5-day abnormal return (CAR[0,+5]) by net-added-risk quintile, Q1 (fewest changes) to Q5 (most). If "Lazy Prices" held cleanly, Q5 would sit well below Q1. It doesn't. The pattern is non-monotonic, and the in-sample and holdout periods disagree.
| Quintile | In-sample % | Holdout % |
|---|
Cross-sectional coefficient on CAR[0,+5] per 1 SD of each signal, controlling for momentum and beta, with filing-date-clustered standard errors. Blue is the Lazy-Prices direction; red is against it. All p-values are above 0.05, including the transparent Loughran-McDonald tone deltas, a cross-check that the null isn't an artifact of one signal.
| Signal | Coef (% / SD) | p |
|---|
Motivated by Didisheim, Kelly, Pourmohammadi and Tian's "The Inefficient Pricing of News" (NBER w35093): represent text as an embedding vector and treat only the part not explained by firm characteristics as news. Here, each Item 1A is embedded locally (bge-large, chunked and mean-pooled) and the signal is the cosine distance between consecutive years, plus a residualized version that strips what sector and section size predict, fit on prior years only so nothing leaks backward.
| Test (in-sample, 2,529 events) | emb_delta | residualized |
|---|---|---|
| Placebo [-5,-1] (want insignificant) | p=0.34, clean | p=0.82, clean |
| CAR[0,+5] coef per 1 SD | -0.10%, p=0.56 | -0.10%, p=0.63 |
| CAR[0,+21] coef per 1 SD | +0.30%, p=0.19 (wrong sign) | +0.40%, p=0.13 (wrong sign) |
| Long-short spread, net of costs | -0.43% (p=0.47) | |
Same verdict as the mechanical count: nothing significant, the sign flips across horizons, and the spread is negative after costs. That is informative. The paper's version works on monthly news across thousands of names; at one filing per company per year there are too few independent bets for a faint text effect to clear costs. In-sample only, stated plainly: the holdout was spent on the original signal, and judging this one honestly would need a fresh out-of-time window, looked at once.
A 30-name subsample showed a strong, significant holdout effect. The full 500 washes it out, a textbook demonstration of why out-of-sample, full-universe, net-of-cost discipline exists. The methodology is the point:
Not claimed: net_added is a blunt mechanical count. A diff-grounded LLM classifier
sharpens it into "substantive new risks vs boilerplate"; a small demonstration is below, and whether the sharpened
signal survives the harness is the documented next step.
net_added treats every added sentence equally. A diff-grounded classifier (Claude
Opus, shown only the changed sentences, never the full filing) labels each one: genuinely new risk, expansion,
rewording, or boilerplate. Four sample runs, one model call each:
| Company | 10-K | Added (mechanical) | Substantive (LLM) | Brand-new risks |
|---|---|---|---|---|
| AAPL | 2025 | 51 | 35 | 16 |
| BA | 2026 | 31 | 24 | 9 |
| NVDA | 2026 | 65 | 57 | 43 |
| JPM | 2026 | 79 | 55 | 18 |
Example labels, straight from the runs:
Roughly a quarter to a third of mechanically-added sentences are noise the classifier filters out, and the share varies by company (NVDA 88% substantive, JPM 70%).
Browse it yourself. The same classifier was then run once over every company's latest 10-K (473 companies, 13,208 added sentences) using Claude Sonnet 5 through the batch API, and the labels were saved as static files. Pick a company to see exactly what the LLM decided, sentence by sentence; hover a label for its one-line reason. Nothing runs live here: you are reading a cached, one-time run. Lists are capped at the first 40 added sentences per filing. On the four Opus-labeled names above, Sonnet's substantive share agrees closely (NVDA 88% in both runs); the line between "new risk" and "expanded" is more model-sensitive, which is exactly why the sharpened signal counts both.
Loading the label index…
Honest framing: these labels cover each company's latest 10-K, so you can
inspect what the classifier does. The study's headline numbers still use the mechanical count; scoring
n_substantive_added across all 4,705 historical events under the same harness (holdout, net of costs)
is the documented next step.