Quant500 Academy

3. Mergers, spin-offs, ticker changes and delistings

Level 3: Adjustments and corporate actions

A split and a dividend change the scale of a series. The corporate actions in this module do something worse: they change the identity of what the series is measuring, or they end it. And a series that ends does not always mean the same thing.

Mergers: what happens to the acquired company

When A takes over B, B's series comes to an end, but B's shareholder is not left empty-handed. They receive cash, shares in A, or a mixture. What your database has to record is the real final return of that last session, not a gap.

Suppose each share in B is exchanged for 0.75 shares in A, that B was trading at 30.00 and that A closed at 44.00 on B's last day. B's shareholder receives 0.75 times 44.00, that is 33.00 of value for shares that were worth 30.00: a final return of +10.0%. If your series stops at 30.00 and records nothing further, you have erased that 10%, which also tends to be positive because acquisitions are paid at a premium.

On A's side there is nothing to adjust for the exchange itself: A issues new shares and in return takes on a business, so its price makes no artificial jump. The typical error on the acquirer's side is a different one and it is an accounting error: its fundamentals suddenly start to include a business that was not there before, so revenue or earnings per share series make jumps that are legitimate but brutal.

Spin-offs: the most forgotten adjustment

A spin-off is the opposite. The parent hands its shareholders shares in a subsidiary that goes on to trade in its own right. The shareholder still holds the same thing, split across two pieces of paper. And the parent's price falls all at once, exactly as with a dividend, only here the amount can be enormous.

import pandas as pd

# 1-for-1 spin-off: the parent closes at 66.00 the day before; on the ex date
# the parent closes at 28.00 and the spun-off company at 38.00.
cierre_matriz_ex, cierre_escindida_ex, razon = 28.00, 38.00, 1.0
valor_entregado = razon * cierre_escindida_ex

# We want the return on the ex date to come out at 0.00%.
factor = cierre_matriz_ex / (cierre_matriz_ex + valor_entregado)
print(round(factor, 6))     # 0.424242

matriz = pd.Series(
    [64.00, 65.50, 66.00, 28.00],
    index=pd.to_datetime([
        "2015-07-15", "2015-07-16", "2015-07-17", "2015-07-20",
    ]),
)
fecha_ex = pd.Timestamp("2015-07-20")
ajustada = matriz.copy()
ajustada[matriz.index < fecha_ex] *= factor

print(round(matriz.pct_change().iloc[-1], 4))      # -0.5758  false
print(round(ajustada.pct_change().iloc[-1], 4))    #  0.0000  correct

A minus 57.6% that never happened. eBay spun off PayPal on July 20, 2015, handing over one PayPal share for each eBay share, and eBay's quoted price roughly halved that day without anybody losing anything. A series without this adjustment turns that transaction into the worst day in the company's history.

Ticker changes: the event with no factor

A symbol change has no economic content: the factor is 1. Its danger is of another kind. Facebook started trading as META on June 9, 2022. If your loader joins prices, fundamentals and signals on the symbol column, that date splits the company in two: a history that dies in June 2022 and a company born that day with no past. Any 200-session window, any twelve-month momentum and any volatility estimate sit empty for almost a year.

There is an aggravating factor: released symbols get reassigned. Months later, another company may be trading under the old symbol, and your text-based join will chain two different companies into a single continuous series, with a price jump in the middle that will look like a market event.

Delistings: the same silence, two opposite meanings

When a company stops trading, the series simply halts. That silence can mean two opposite things.

# The delistings table stores the reason and what was collected, not just the cut-off date.
# The last closes are rounded, by way of illustration.
bajas = pd.DataFrame({
    "id_permanente": [11111, 22222],
    "ultima_sesion": pd.to_datetime(["2022-10-27", "2008-09-16"]),
    "motivo": ["adquisicion en efectivo", "quiebra"],
    "cobrado_por_accion": [54.20, 0.00],
    "ultimo_cierre": [53.70, 0.20],
})

bajas["retorno_final"] = bajas["cobrado_por_accion"] / bajas["ultimo_cierre"] - 1
print(bajas[["motivo", "retorno_final"]].round(4))
# adquisicion en efectivo     0.0093
# quiebra                    -1.0000

Without the reason column and without the amount collected, those two rows would be indistinguishable. Treating them the same is an expensive error, and in either direction. If you assign minus 100% to all of them, you unfairly punish the ones bought at a premium. If you carry the last known price through to the end of the period, you hand Lehman's shareholder capital that evaporated. And if you simply exclude from the universe the ones that did not make it to the end alive, you are no longer measuring a strategy: you are measuring a list of survivors.

A real case: October 2022 against September 2008

Put the two series side by side. Both stop dead. Both cease to appear in the next day's price file. A naive loader sees them as identical: two gaps.

And they are opposites. One ended with a cash payment of $54.20 a share; the other with a residual value close to zero. That is why a serious vendor's delistings table does not store only a date: it stores the reason, the consideration and the final return of the last stretch. Without that field, your database cannot tell an exit through the front door from a bankruptcy, and the difference between the two can be 100% of the capital invested in that position.

To go deeper into this lesson

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.