DeskTested

Trading tools, tested on a real desk.

Updated Oct 7, 2026GuideClaims verified ✓Anthony M.

Options Backtest Stock Split Adjustment: How a Reverse Split Faked a +78% Week

Unadjusted options data turns a stock split into a fake return. How a 1:20 reverse split broke our backtest, the rule that fixed it, what to ask a tool.

An options backtest needs explicit split handling because end-of-day option chains and underlying prices are usually recorded as they traded, not adjusted for later stock splits. Join them across a split date and the simulation books a price jump that never happened. In our own put-selling simulation, one 1:20 reverse split produced a week of +78% “premium” and +146% “true” return (backtest simulation — not live results, not a prediction, not advice). Neither number was real, and both were thrown away.

This page explains how that happens, the rule we added to stop it, and the questions worth asking any options backtester about corporate actions. The discarded figures appear here only as the defect, never as a result.

What happened in our run

The simulation came from our study of a public “1% a week” put-selling series. It priced every trade from end-of-day option chain snapshots over 121 Fridays, from 7 June 2024 to 25 September 2026. The findings themselves live on the put-selling page and aren’t repeated here. This is about one defect in the plumbing.

The first version of the run used 19 names. The list it was meant to copy had 20, and the missing one was ASST. When ASST was added, the simulation started printing impossible weeks: +78% “premium” in a single week and +146% “true” return in a single week (backtest simulation — not live results, not a prediction, not advice). The desk record is blunt about what they were: “those numbers were discarded, never logged.”

The cause was a corporate action. Three splits fall inside the test window, and the end-of-day prices and contracts as our simulation consumed them were not adjusted for those splits. That describes the desk’s own dataset and how the sim joined it, not any vendor’s documented behaviour:

Underlying Corporate action First post-split trading day New shares per old share Unadjusted price factor Fake move in an unadjusted series
MSTR 10:1 forward split 2024-08-08 10 ×0.1 −90%
FCEL 1:30 reverse split 2024-11-11 1/30 ×30 +2,900%
ASST (Strive) 1:20 reverse split 2026-02-06 1/20 ×20 +1,900%

The first four columns come from the desk record. The last two are arithmetic from the ratio, holding the company’s value constant.

Bar chart of the fake close-to-close move each split leaves in an unadjusted price series: MSTR 10:1 forward split -90%, FCEL 1:30 reverse split +2,900%, ASST 1:20 reverse split +1,900%

Why the bug hid until the 20th name

Two of the three splits were in the universe from the start, and the run looked clean. The record explains why: “Only one position per variant ever straddled a split (ASST).” Neither MSTR nor FCEL was held across its split date, so the bad join was never exercised.

That’s the dangerous property of this defect. A backtest can carry a split bug through every check you run and look fine, because no open position happens to cross the date. The bug only shows up when the universe, the entry rules or the dates change enough to put a position across a split. Here that took one name that had been dropped by mistake.

Unadjusted data isn’t a mistake in itself, and for options it’s often what you want. The bid and ask a contract actually traded at on a given day is the honest input for fills, a point our fill-assumptions page makes at length. The defect is joining those raw prices across a split as if the share count never changed. (The related trap with split-only versus dividend-adjusted stock series is one of the data defects in our backtesting pitfalls write-up.)

How an unadjusted join manufactures a return

The desk record doesn’t break the +78% and +146% weeks down line by line. The mechanics below are a hypothetical illustration with round numbers, not ASST’s actual prices. They show how both kinds of fake number come out of a simulation that ignores a 1:20 reverse split.

# Hypothetical illustration: round numbers, not market data.
# 1:20 reverse split: every 20 old shares become 1 new share.
ratio = 20
pre_close, post_close = 1.00, 20.00   # same company value, 1/20th the share count

# (1) Stock held across the split (e.g. after a put assignment)
shares = 100
naive_value = shares * post_close             # old share count x new price = $2,000
true_value  = (shares / ratio) * post_close   # 5 shares x $20 = $100
cost        = shares * pre_close              # $100
print(f"naive {naive_value / cost - 1:+.0%}  true {true_value / cost - 1:+.0%}")
# naive +1900%  true +0%

# (2) Short put held across the split
strike, premium = 1.00, 0.10
# A naive lookup prices the old $1 strike against a $20 stock:
# 95% below the money, so near zero. The full premium gets booked as profit,
# even though the real (adjusted) contract still carries the same risk.
naive_buyback = 0.00
print(f"booked as profit: ${(premium - naive_buyback) * 100:.0f} per contract")

Both errors point the same way: gains that never happened. A reverse split makes the stock look like it soared, and every short put on it suddenly looks safely out of the money. A forward split does the opposite and fakes a crash. That’s why the MSTR row matters even though it didn’t trigger here. In a real account the listed contracts are adjusted rather than left alone. That usually means a changed deliverable or contract count, sometimes under a new option symbol. A simulation that keys on symbol and strike can lose track of the position entirely.

The rule that fixed it: never hold through a split

The fix the desk adopted is the simplest one that removes the problem. From the record: “the sim now never holds a name across its split: on the last Friday before the split it closes that name at that day’s real quotes and opens nothing in it until the first Friday after.”

The desk’s code keeps a table of split dates and a forced-close block. The version below is a simplified illustration of that rule, not the desk’s code:

# Illustrative, simplified. Weekly options, decisions made on Fridays.
from datetime import date, timedelta

SPLITS = {"MSTR": date(2024, 8, 8), "FCEL": date(2024, 11, 11), "ASST": date(2026, 2, 6)}

def split_action(sym: str, friday: date) -> str:
    eff = SPLITS.get(sym)
    if eff is None:
        return "trade"
    last_before = eff - timedelta(days=((eff.weekday() - 4) % 7) or 7)
    first_after = eff + timedelta(days=((4 - eff.weekday()) % 7) or 7)
    if friday == last_before:
        return "close_at_real_quotes"   # exit at that day's actual bid/ask
    if last_before < friday < first_after:
        return "no_new_positions"
    return "trade"

For the three splits in the window, that gives these blocked stretches:

Underlying First post-split trading day (weekday) Forced close on Trading resumes on
MSTR 2024-08-08 (Thu) 2024-08-02 2024-08-09
FCEL 2024-11-11 (Mon) 2024-11-08 2024-11-15
ASST 2026-02-06 (Fri) 2026-01-30 2026-02-13

The trade-off is explicit. A real trader could have held through the split, and the rule gives up whatever that position would have done. In exchange, every price the simulation uses is one the contract actually traded at, and no week’s result depends on a ratio conversion. The other route is to carry each position through the split by converting strikes, contract counts and deliverables. That’s more faithful, but every one of those conversions is another place for a bug. With only one straddling position per variant, the forced close cost little and removed the whole class of error.

How to check your own options backtest for split errors

You don’t need to know how your data is adjusted to find this bug. You need to look in the right places.

  1. List the corporate actions inside your window for every name in the universe, before you run anything.
  2. Look for impossible weeks. A single-week return on a short-premium book that’s larger than the premium collected is a data error until proven otherwise. So is any week where the underlying’s close moves by a clean multiple (×10, ×20, ×1/10).
  3. Find out which positions straddle a split. If none do, your split handling hasn’t been tested, however clean the results look.
  4. Re-run after changing the universe. A latent bug like ours only appears when a new name puts a position across a split date.
  5. Write down the rule. “Close before, reopen after” and “convert through” are both defensible. An unstated mix of the two is not.

The same discipline runs through how we validate a strategy: check the data before the rule, because a defective series returns rows, not errors.

Questions to ask any options backtester about corporate actions

These questions apply to any options backtester. We make no claim here about how any named vendor or tool answers them. Read the answer in its own documentation.

  • Are underlying prices and option strikes adjusted for splits, and back to what date? Adjusted and unadjusted series both have legitimate uses. You need to know which one you’re getting.
  • What happens to an open simulated position across a split? Is it converted, closed or dropped, and is that disclosed in the trade log?
  • Are adjusted contracts (non-standard deliverables, new option symbols) carried forward or treated as new instruments?
  • Are reverse splits handled the same way as forward splits? A reverse split fakes a gain where a forward split fakes a loss, so a tool tested only on forward splits can still mishandle the other kind.
  • Can you export the raw quotes behind a single trade? If you can’t see the bid and ask a fill used, you can’t check a suspicious week.

For one options data source’s documented coverage, see our ThetaData review, and for how vendors’ tiers compare, our options data API comparison. Whatever the source, confirm its split handling in the vendor’s own documentation.

Frequently asked questions

Do options backtests need split-adjusted data?

They need a stated split policy more than they need any particular adjustment. Raw end-of-day option quotes are the honest input for fills. They just can’t be joined across a split date without converting share counts, strikes and deliverables. You can convert positions through the split, or close before it and reopen after, as our simulation does.

How can a reverse split create a fake profit in a backtest?

A 1:20 reverse split multiplies the share price by 20 while dividing the share count by 20. A simulation that keeps the old share count and reads the new price records a +1,900% move on the stock. It also prices short puts as far out of the money when the real, adjusted contracts carry the same risk as before.

Was the +78% week a real result?

No. It was a backtest simulation artifact (not live results, not a prediction, not advice), produced by a 1:20 reverse split in unadjusted data, and the desk record states those numbers “were discarded, never logged.” They appear on this page only as the defect the forced-close rule removed.

Why didn’t the MSTR and FCEL splits break the backtest too?

Neither name had a position open across its split date in any variant, so the faulty join was never used. Only ASST had a position straddling its split. That’s why the bug stayed hidden until ASST was added to the universe.


This is educational content about backtesting method, not financial advice. No positions, live signals, account values or trading results appear on this page. Simulation details are from our own research records, not live results, not a prediction, not advice.