‹ Backtest Strategies Lesson 3 of 17
Contents Lesson 3 of 17

4 min read · practitioner

Turn prices into returns, before any strategy exists

No rules yet. This lesson is arithmetic, and getting it wrong invalidates everything built on top — which is why it comes before anything interesting.

A return is a ratio, not a difference

const r = bars[i].adjusted_close / bars[i - 1].adjusted_close - 1;

A price series is not comparable across time or across instruments; a return series is. Two rules follow immediately, and both are about which column you divide.

The column that decides whether your numbers mean anything

Here are five consecutive AAPL bars captured live, spanning 2020-08-31:

[{"date":"2020-08-27","close":500.04,"adjusted_close":121.1519},
 {"date":"2020-08-28","close":499.23,"adjusted_close":120.9557},
 {"date":"2020-08-31","close":129.04,"adjusted_close":125.0575},
 {"date":"2020-09-01","close":134.18,"adjusted_close":130.0389},
 {"date":"2020-09-02","close":131.40,"adjusted_close":127.3447}]

Compute the 2020-08-31 return both ways:

  • On close: 129.04 / 499.23 − 1 = −74.2%
  • On adjusted_close: 125.0575 / 120.9557 − 1 = +3.4%

Nothing bad happened to Apple that day. It was a four-for-one stock split, confirmed by the splits endpoint:

{"date":"2020-08-31","split":"4.000000/1.000000"}

Note the shape: the ratio arrives as a string, not a number. Parsing it is your problem.

A backtest on the raw close column sees a −74% day. If your rule buys crashes, it just bought a phantom. If it stops out on losses, it just stopped out of nothing. Use adjusted_close for returns. Every time.

Why raw close still exists

Because it is what actually printed, and some questions need it: what you would have paid per share, whether a limit at 130 filled, what the position was worth in dollars on the day.

The rule is nearly clean: returns and signals on adjusted, execution arithmetic on raw — with one caveat worth saying out loud in a unit about information that existed at the bar. Today's adjusted series already contains every dividend paid since, so a level-based rule near an ex-date on a high-yield name is slightly contaminated by a payment that had not happened yet, and none of the leak tests below can see it, because the data arrives that way. Label the two columns in your code so nobody — including you next month — has to guess which is which.

Compounding, and the mistake beneath it

To turn a return series into an equity curve you multiply, never add:

let equity = 1;
for (const r of returns) equity *= 1 + r;

Adding returns overstates gains and understates losses, and the error grows with the number of periods. The intuition: −50% then +50% is not flat, it is −25%. Any code path that sums percentages is wrong, and it will look approximately right on short samples, which is what makes it dangerous.

The finance behind it

Two lessons stand behind the adjusted column — what it is for, and why it keeps changing: Why do old prices look "wrong" on charts? and Why does your price history rewrite itself every quarter?

Try it now

Compute the daily return series for one symbol twice, once on each close column, and print the ten largest moves from each. The raw-close list will be full of splits pretending to be crashes. That list is your evidence, and it takes about four lines.