A model can be correct and still produce nonsense, if the data feeding it is wrong in a way nobody checked. Corporate actions are one of the most common ways that happens, and one of the easiest to miss, because the failure usually looks like a plausible number rather than an error.
The mechanics are mundane. A company splits its stock, and the price drops by the split factor overnight with no economic change. It pays a dividend, and the close the next morning steps down. It spins off a division, merges with a rival, or changes its ticker. Each of these events alters the raw series in a way that has nothing to do with return. If you compute a daily return across a four-for-one split without adjusting for it, you will read a 75 percent loss that never happened. Feed that into a model and it learns from a price move that is purely an accounting artifact.
Adjusted versus unadjusted, and why the distinction matters
There are two legitimate views of a price history, and confusing them is where most of the damage starts.
The unadjusted series is what actually printed on the tape that day. It is the right thing to use when you care about the real traded price, settlement, or any calculation that has to match what a counterparty saw.
The adjusted series back-applies a split adjustment and dividend reinvestment to the historical prices so that a continuous return calculation is correct across the event. Adjustment is what you want for returns, momentum, volatility, and almost anything a model trains on. The catch is that an adjusted series is not a fact about the past, it is a derived quantity that changes every time a new action occurs. The closing price your dataset reports for some day three years ago will be a different number after the next split, even though nothing about that day changed. That is correct behaviour, but only if your pipeline knows it and your downstream consumers do too.
Getting the split adjustment right means more than scaling prices. Volume scales inversely, dividends have their own reinvestment logic, and the order in which you apply overlapping actions matters. A pipeline that handles splits but ignores special dividends will be subtly wrong, and subtly wrong is the hardest kind to catch.
Identifier reuse breaks entity matching
The harder problem is identity. Tickers get recycled. A symbol that belonged to one company can, after a delisting or merger, be reassigned to a completely different one. Identifier changes also run the other way, where the same economic entity carries different symbols across vendors, or changes its own over time. If your join key is a ticker and nothing else, a spin-off or a reused symbol will silently stitch two unrelated companies into one series. The chart looks continuous. The history is a fiction.
This is where corporate actions stop being a pricing concern and become an entity resolution one. The questions we work through on every feed:
- Does each price record carry a stable internal entity ID, not just the vendor symbol that happened to be active that day?
- When a ticker is reused, does the pipeline split the series at the handover date rather than concatenating across it?
- For a merger or spin-off, is the corporate-action calendar reconciled against the price feed, so the break shows up in both?
- Are vendor identifier changes recorded with effective dates, so a point-in-time query resolves the symbol that was correct then, not now?
Keeping it point-in-time
The last piece is timing. A corporate action has an announcement date and an effective date, and they are rarely the same. A backtest that adjusts prices using the final ratio before the action was even announced is reading the future. That is lookahead, and it flatters results in exactly the way that survives a casual review and fails in production.
So the adjustment has to be vintaged. The series a backtest sees on a given historical day should reflect only the actions known and effective by that day, not the full set we have now. This is slower to build than a single fully-adjusted table, and it is the only version that tells you what a strategy would actually have done.
A missed action does not announce itself. It corrupts a return, that return corrupts a feature, the feature corrupts the model, and the entity-matching error quietly contaminates everything joined on it. By the time the eval score drifts, the cause is several layers upstream. The cheaper path is to treat the corporate-action calendar as a first-class input, reconcile it against the price feed, and keep the whole thing auditable back to source. Tedious, and far less expensive than discovering it later.