Quant from Scratch by QuantWright

Backtesting Essentials · Lesson 8

Overfitting

When a rule memorises the past: why it can look far better on the data it was tuned on than on data it has never seen.

Know the basics? Read the lesson notes

A rule with two settings

A backtest runs a trading rule over past prices to see what it would have made. This lesson's prices are simulated: a computer made them up. Start with five years, 2015 to 2019.

Each day, a rule either holds the market or stays out. Holding a day means putting $100 in at the day's first price, its open, and taking it out at its last, its close. A rule's result is what its days made and lost, added up, with no trading costs. Just holding every day made $24 over the five years.

We built this market, so we know what is real in it. One thing: after each close the market publishes a made-up number, the reading, and the next day's move tends to go a little the reading's way, up after a reading above 0 and down after one below. The weekday and the month change nothing.

The condition "was yesterday's reading above 0?" sorts the days into two kinds: days after a high reading and days after a low one. The rule gets one setting for each kind: hold, or stay out. Picking the settings that give the best backtest is called tuning. Here that means: hold a kind of day if its days added up to a gain over these five years.

One rule, tuned on 2015–2019 (simulated)

Colour: the rule's result so far. Grey: just holding. Boxes: its settings, filled for hold.

Conditionsnone
Settings0
Result, 2015–2019$24
Just holding$24

Days after a high reading added up to +$37, so their setting is hold. Days after a low reading added up to −$13, so theirs is stay out. Just holding took both: 37 − 13 = $24.

Keep adding conditions

Can more conditions beat $37? Add the weekday: 2 × 5 = 10 kinds of day, each with its own setting. Then add the month: 10 × 12 = 120 kinds, 120 settings. So each condition added multiplies the settings. Each time, every setting is tuned again on the same five years.

The same rule with more settings, tuned on 2015–2019 (simulated)

Lighter lines: the rules before. Boxes: its settings, filled for hold.

Conditionsthe reading
Settings2
Result, 2015–2019$37
Just holding$24

Each condition lifted the result: $37 with the reading, $65 with the weekday, $150 with the month. The month added the most, 150 − 65 = $85, and the month changes nothing in this market (a later screen shows how that can be). The reading, the one real thing, had added 37 − 24 = $13.

A guess before the new years

The 120-setting rule made $150 on 2015 to 2019. Call those the tuning years: the years its settings were picked on.

The market runs on for five more years, 2020 to 2024, built the same way. The tuning never saw them: the new years. The rule keeps its 120 settings as tuned. What will it make there?

Keep your guess in mind for the next screen.

Five years it has never seen

Left: the tuning years again. Right, shaded: the new years. Each side starts from $0. Run the new years.

The 120-setting rule: tuning years, then new years (simulated)

Colour: the tuned rule. Grey: just holding.

2015–2019: rule · holding$150 · $24
2020–2024: rule?
2020–2024: holding?

On the new years the rule made $16. Just holding made $27 there, so the tuned rule did 27 − 16 = $11 worse than no rule at all. Of its $150, 150 − 16 = $134 was not there on the new years.

The settings were the best possible ones for 2015 to 2019. That is the trouble: most of them fit what luck did in those years, and luck doesn't repeat. A rule whose settings fit the luck in its tuning years is overfit.

Fewer settings, then more

You made four rules on the way: just holding (0 settings), then one, two and three conditions. Slide through them and compare the tuning years with the new years.

Four rules on the tuning years and on the new years (simulated)

Dark: the tuning years. Colour: the same rule, same settings, on the new years.

Settings0
Tuning years$24
New years$27
Tuning minus new–

On the tuning years every added condition lifted the result. When each new condition only splits the old kinds of day further, tuning can never lower the result there: the finer rule can always set its extra settings the same way as the old rule.

On the new years the result first rose, from $27 to $55 with the reading, then fell: $36 with the weekday, $16 with the month. The reading is real, so what tuning found in it was still there. The weekday and month settings fit only luck, and on the new years they did harm.

The figure's last line is the tuning gap: the tuning-years result minus the new-years result. For 2 settings it is below zero, −$18, because luck sometimes favours the new years.

How can a condition that means nothing help?

Look inside the 10-setting rule. Each pair of bars is one of its ten kinds of day: its total on the tuning years, and on the new years.

Ten kinds of day: tuning years and new years (simulated)

Dark: 2015–2019, filled if tuning set the kind to hold, hollow if stay out (a total the rule left out). Colour: 2020–2024.

Against the 2-setting rule, tuning changed three settings and left seven. Take Mondays after a high reading. On the tuning years those 139 days lost $7 in total, so tuning set them to stay out. On the new years the same kind of day made $21, and the rule missed it.

The other two, Mondays and Wednesdays after a low reading, had made $9 and $12, so tuning set them to hold: 7 + 9 + 12 = $28, the step from $37 to $65. On the new years they made $2 and $1. The rule gave up $21 to gain $3: the drop from $55 to $36, give or take a dollar of rounding.

The weekday changes nothing in this market. Why did those Mondays lose money on the tuning years?

Nothing in this market makes one weekday differ from another: we built it that way. On the new years the same kind of day made $21. The $7 loss was luck.
Nothing in the market makes Mondays differ. A pile of 139 days has its own luck: $7 down on the tuning years, $21 up on the new ones.
Tuning did its job: on 2015 to 2019, sitting those Mondays out was better by $7. The loss itself was luck, so there was no reason for it to repeat, and on the new years those days made $21.
The part of a past result that came from luck is called noise. Each setting is tuned on its own pile of days: about 650 of the 1,300 days with 2 settings, about 130 with 10, about 11 with 120. The smaller the pile, the more of its total is noise, so more settings give tuning more noise to fit.

Was that just this one market?

One market can be lucky or unlucky. So we built 1,000 more markets the same way, each with its own luck, and did the same on each.

A guess first. In how many of the 1,000 does the 120-setting rule beat the 2-setting rule on the new years?

The four rules on 1,000 simulated markets: average results

Dark: the average result on the tuning years. Colour: the average on the new years.

Markets run0
120 settings beat 2 on the new years–
Average tuning gap, 120 settings–

The 120-setting rule beat the 2-setting rule on the new years in 129 of the 1,000 markets; the 2-setting rule did better in the other 871.

The averages. Tuning years: $36, $62, $77, $177. New years: $38, $56, $44, $28: up with the reading, then down, to 38 − 28 = $10 less than just holding.

For the preciseIn all 1,000 markets the 120-setting rule made less on the new years than on its tuning years; it fell below just holding there in 661. The 2-setting rule beat just holding on the new years in 653 markets: five years are short for an effect this small. Its tuning gap was above zero in 545 markets and below zero in 449. Best rule on the new years: no condition in 191 markets, 2 settings in 520, 10 in 207, 120 in 82. Averages to one decimal, tuning years: 35.7, 61.9, 77.1, 176.8; new years: 38.4, 56.3, 43.9, 27.6. Differences in the text are taken between the whole-dollar figures shown, so they can be a dollar away from the difference of the one-decimal figures: 38 − 28 = $10 in the text, 38.4 − 27.6 = 10.8 here.
Theory

The tuning gap

Overfitting is tuning a rule until its settings fit the noise in the tuning data as well as anything real in it (another name: curve fitting). It shows on new data: data that played no part in tuning the rule or in choosing it.

tuning gap = tuned − new
tuned
a rule's result on the data its settings were tuned on
new
the same rule's result, with the same settings, on new data of the same length from the same market
tuning gap
the part of a tuned result that was not there on new data. A large tuning gap is a sign of an overfit rule
Worked example: your rules

120 settings: 150 − 16 = $134. 2 settings: 37 − 55 = −$18.

How big is large? Read the gap next to what just holding made on both stretches: $24 and $27 here, about the same. A gap of $134 is about five times $27.

One market's tuning gap has luck in it. Averaged over the 1,000 markets it was $6 with 2 settings and $149 with 120.

Holds when
  • the new data played no part in tuning the rule or in choosing it;
  • the two stretches are the same length.
Breaks when

The new data has been used to choose a rule: then it is tuning data too (lessons B9, In-sample, out-of-sample and walk-forward, and B10, The held-out test).

New numbers. A rule tuned on eight years made $84 there. With the same settings it made $21 on the next eight years. What is the tuning gap, in dollars?

84 − 21 = $63.
tuning gap = tuned − new = 84 − 21 = $63. Of the $84, $63 was not there on the next eight years.
Theory

Every setting multiplies the versions

A version is one full set of settings, and every combination of settings is its own version. To count them, multiply together the number of values each setting can take. Tuning keeps the best version: more versions, more luck fitted (lesson B7, The best of many), and a bigger tuning gap to expect.

versions = c1 × c2 × … × ck
k
the number of settings a rule has
c1, c2, …, ck
how many values the first setting can take, the second, and so on to the last (2 for an on/off setting)
Worked example: your rules

2 settings, each hold or stay out: 2 × 2 = 4 versions. On the tuning years: hold both kinds of day, $24 (just holding); hold only after a high reading, $37; hold only after a low reading, −$13; hold neither, $0. Tuning kept the best, $37.

10 settings: 2 multiplied by itself 10 times, 1,024 versions. 120 settings: 2 multiplied by itself 120 times, far too many to run one by one.

Tuning did not need to: each kind of day adds its own total to the result, so the best setting for each kind gives the best version of all.

Holds when
  • every value a setting could take is counted, the ones never looked at too.
Breaks when

Many versions are near copies: two that differ in one setting out of 120 hold almost the same days, so they add less luck than two separate tries. The count is then an upper limit on the size of the search (lesson B7's caveat; lesson B14, Robustness).

New numbers. A rule has 6 on/off settings. How many versions can tuning choose from?

12 is 6 × 2: that adds the settings up. Each on/off setting doubles the versions: 2 × 2 × 2 × 2 × 2 × 2 = 64.
36 is 6 × 6. The rule multiplies the number of values of each setting, and each has 2: 2 × 2 × 2 × 2 × 2 × 2 = 64.
2 × 2 × 2 × 2 × 2 × 2 = 64. Six small switches already make 64 versions.

What this means for your backtest

Three habits follow.

  1. Count the settings, and the versions they make.

    Every setting you can adjust multiplies the versions, and each version is one more try at fitting luck (lesson B7). Count the days or trades behind each setting too: 120 settings on 1,300 days left about 11 each.

Practice 1 of 3

"Optimised on ten years of data"

The situationImagine a course seller showing a trading system "optimised on ten years of data". It has 14 settings, each tuned, and its backtest over those same ten years made five times what just holding made.

What is the first thing to ask?

The backtest covers the tuning data, where every tuned setting helps whether it fits something real or noise. Only years the tuning never saw can show how much of it is still there.
Finer tuning fits the ten years more closely, their noise included. It makes the tuned result better and says nothing about other years. Ask how the system did, with the same settings, on years the tuning never saw.
Ten years of tuning data are still tuning data: every tuned setting helps there, whether it fits something real or noise. Ask how the system did, with the same settings, on years the tuning never saw.
Practice 2 of 3

Count your own search

The situationYour own research. Your rule has four settings: how many past days it reads (12 possible values), the level at which it buys (5 values), how many days it holds (4 values), and a weekday filter that is on or off.

If you tune all four, how many versions is the tuning choosing from?

Hint: multiply together the number of values each setting can take. On or off is 2 values.

12 × 5 × 4 × 2 = 480 versions. The tuned backtest shows the best of 480, on the data that picked it.
Multiply, don't add: 12 × 5 × 4 × 2 = 480 versions (adding gives 23). The tuned backtest shows the best of 480, on the data that picked it.
Practice 3 of 3

Two rules, one pick

The situationA friend tuned two rules on the same eight years. Rule A has 3 settings and made $48 there. Rule B has 40 settings and made $160 there. Neither has been run on any other data.

Which statement is right?

On tuning data, more settings give a better result whether they fit something real or noise: the lesson's 120 settings made $150 on the tuning years and $16 on the new ones. B's $160 can't be compared with A's $48 until both have run on data that neither tuning saw.
Both numbers come from the data the settings were tuned on, and B's 40 settings had far more room to fit noise than A's 3. The comparison that counts is on data neither tuning saw.
Fewer settings leave less room to fit noise, but that doesn't make A the better rule: 3 settings can fit nothing real, and 40 can hold something real among the noise. Only data that neither tuning saw can rank them.
✓

Lesson done

Tuning picks the settings that did best on past data, so a tuned rule's result there never falls as settings are added, and usually rises, real or not. In the lesson's simulation, on years the tuning never saw, only the one real condition helped. The first result minus the second is the tuning gap; a large one is a sign of overfitting (one gap has luck in it). So judge a tuned rule on data that played no part in tuning or choosing it.

Key ideas

Setting
anything in a trading rule that can be adjusted: a number of days, a level, an on/off switch
Version
one full set of settings of a rule
Tuning
picking the settings that give a rule its best backtest on a chosen stretch of data, the tuning data
Noise
the part of a past result that came from luck and has no reason to repeat
Overfitting
tuning a rule until its settings fit the noise in the tuning data as well as anything real in it (also: curve fitting)
New data
data that played no part in tuning a rule or in choosing it
Tuning gap
a tuned rule's result on its tuning data minus its result, with the same settings, on new data of the same length

Formulas

tuning gap = tuned − new

versions = c1 × c2 × … × ck

Lesson notes

Tuning a trading rule means picking the settings that did best on past data. The result on that data never falls as settings are added, and usually rises, whether the settings fit something real or luck. In the lesson's simulation, on data the tuning never saw, only the real part was still there. The first result minus the second is the tuning gap; a large one is a sign of overfitting (one gap has luck in it).

A rule, its settings and tuning

A backtest runs a trading rule over past prices to see what it would have made. The lesson's prices are simulated: a computer made them up. Each day a rule either holds the market or stays out: holding puts $100 in at the day's first price, the open, and takes it out at its last, the close. A rule's result is what its days made and lost, added up, with no trading costs. Just holding every day of 2015 to 2019 made $24.

One thing in this market is real, because it was built in: after each close the market publishes a made-up number, the reading, and the next day's move tends to go a little the reading's way. The weekday and the month change nothing.

A condition sorts the days into kinds: "was yesterday's reading above 0?" gives days after a high reading and days after a low one. The rule has one setting for each kind: hold, or stay out. Tuning is picking the settings that give the best backtest: here, hold a kind of day if its days added up to a gain over the five years. Days after a high reading added up to +$37 and days after a low one to −$13, so the tuned rule holds only the first kind: $37, against 37 − 13 = $24 for just holding.

More conditions, a better backtest

Adding the weekday gives 2 × 5 = 10 kinds of day, each with its own setting; adding the month, 10 × 12 = 120. Tuned on the same years, the 10-setting rule made $65 and the 120-setting rule $150. The month, which changes nothing in this market, added the most: 150 − 65 = $85. The reading, the one real thing, had added 37 − 24 = $13. Each condition added multiplies the settings. When each new condition only splits the old kinds of day further, tuning can never lower the result on the years it is tuned on, because the finer rule can always set its extra settings the same way as the old rule.

The years the tuning never saw

Call 2015 to 2019 the tuning years and the next five years of the same market, 2020 to 2024, the new years. With its settings unchanged, the 120-setting rule made $16 on the new years. Just holding made $27 there, so the tuned rule did 27 − 16 = $11 worse than no rule at all. Most of its settings fit what luck did in the tuning years, and luck doesn't repeat. Such a rule is overfit.

Across the four rules (just holding, which has 0 settings, then 2, 10 and 120 settings) the tuning-years results were $24, $37, $65 and $150. The new-years results were $27, $55, $36 and $16. For these four rules on this simulated market, that is up with the reading, which is real, then down with the weekday and the month; it is what this order of conditions gave, not a law. The tuning gap is the tuning-years result minus the new-years result: 150 − 16 = $134 for 120 settings. For 2 settings it is 37 − 55 = −$18, below zero, because luck sometimes favours the new years.

How a condition that means nothing helps

Going from 2 settings to 10, tuning left seven settings as the 2-setting rule had them (hold after a high reading, stay out after a low one) and changed three. One is Mondays after a high reading. On the tuning years those 139 days lost $7 in total, so tuning set them to stay out, which lifted the tuned result by $7. Nothing in the market makes Mondays differ; the loss was luck. On the new years the same kind of day made $21, and the rule missed it. The other two, Mondays and Wednesdays after a low reading, had made $9 and $12 on the tuning years, so tuning set them to hold: 7 + 9 + 12 = $28, the whole step from $37 to $65. On the new years they made $2 and $1. The rule gave up $21 to gain $3: the drop from $55 to $36, give or take a dollar of rounding.

The part of a past result that came from luck is called noise. Each setting is tuned on its own pile of days: about 650 of the 1,300 tuning days with 2 settings, about 130 with 10, about 11 with 120. The smaller the pile, the more of its total is noise. More settings mean smaller piles and more noise for tuning to fit.

The same on 1,000 markets

One market can be lucky, so the lesson builds 1,000 more the same way and repeats everything on each. On the new years the 120-setting rule beat the 2-setting rule in 129 markets; the 2-setting rule did better in 871. Average results on the tuning years: $36, $62, $77 and $177. On the new years: $38, $56, $44 and $28, so the 120-setting rule averaged 38 − 28 = $10 less than just holding. That is an average: the 120-setting rule fell below just holding on the new years in 661 of the 1,000 markets, and the 2-setting rule beat just holding in 653, because five years are short for an effect this small.

Three habits

Count the settings, the versions they make and the days or trades behind each setting. Don't read a result on tuning data as evidence: a tuned rule looks good there whether its settings fit something real or noise. Judge a tuned rule on data the tuning never saw, and use that data once: a held-out test is a test on data the tuning never saw, used once. The lesson ran four rules on the new years only to demonstrate; picking the rule that did best there would turn them into tuning data.

Formula: the tuning gap

Overfitting is tuning a rule until its settings fit the noise in the tuning data as well as anything real in it (another name: curve fitting). It shows on new data: data that played no part in tuning the rule or in choosing it.

tuning gap = tuned − new
tuned
a rule's result on the data its settings were tuned on
new
the same rule's result, with the same settings, on new data of the same length from the same market
tuning gap
the part of a tuned result that was not there on new data. A large tuning gap is a sign of an overfit rule
Worked example: the lesson's rules

120 settings: 150 − 16 = $134. 2 settings: 37 − 55 = −$18.

How big is large? Read the gap next to what just holding made on both stretches: $24 and $27 here, about the same. A gap of $134 is about five times $27.

One market's tuning gap has luck in it. Averaged over the 1,000 markets it was $6 with 2 settings and $149 with 120.

Holds whenBreaks when

The new data has been used to choose a rule: then it is tuning data too (lessons B9, In-sample, out-of-sample and walk-forward, and B10, The held-out test).

Formula: counting the versions

A version is one full set of settings, and every combination of settings is its own version. To count them, multiply together the number of values each setting can take. Tuning keeps the best version: more versions, more luck fitted (lesson B7: the best of many tries is partly luck), and a bigger tuning gap to expect.

versions = c1 × c2 × … × ck
k
the number of settings a rule has
c1, c2, …, ck
how many values the first setting can take, the second, and so on to the last (2 for an on/off setting)
Worked example: the lesson's rules

2 settings, each hold or stay out: 2 × 2 = 4 versions. On the tuning years: hold both kinds of day, $24 (just holding); hold only after a high reading, $37; hold only after a low reading, −$13; hold neither, $0. Tuning kept the best, $37.

10 settings: 2 multiplied by itself 10 times, 1,024 versions. 120 settings: 2 multiplied by itself 120 times, far too many to run one by one.

Tuning did not need to: each kind of day adds its own total to the result, so the best setting for each kind gives the best version of all.

Holds whenBreaks when

Many versions are near copies: two that differ in one setting out of 120 hold almost the same days, so they add less luck than two separate tries. The count is then an upper limit on the size of the search (lesson B7's caveat; lesson B14, Robustness).

One more worked example

Made-up round numbers

A rule has three settings with 8, 5 and 3 possible values: 8 × 5 × 3 = 120 versions. One more on/off filter doubles that: 120 × 2 = 240.

Tuned on six years, the best version made $96 there. With the same settings it made $30 on the next six years, which played no part in the tuning: tuning gap = 96 − 30 = $66.

Just holding made $20 on the first six years and $26 on the next six, about the same. The new years were no harsher for a rule that does nothing, so the fall from $96 to $30 points to the tuning, not to a harsher market.

Common mistakes

Check yourself

  1. A rule tuned on four years made $70 there and, with the same settings, $52 on the next four years. What is the tuning gap?
    Answer

    tuning gap = 70 − 52 = $18.

  2. A rule has 5 on/off settings. How many versions can tuning choose from?
    Answer

    2 × 2 × 2 × 2 × 2 = 32 versions.

  3. A rule has three settings with 10, 6 and 2 possible values. How many versions?
    Answer

    10 × 6 × 2 = 120 versions.

  4. In the lesson's market the month changes nothing. Why did adding the month lift the result on the tuning years from $65 to $150?
    Answer

    Each of the 120 settings was tuned on a pile of about 11 days. The smaller the pile, the more of its total is noise. Tuning set each kind of day to whichever side its luck had favoured, so the result on those years rose. On the new years that luck did not repeat.

  5. (from lesson B7) Why does a held-out test count only once?
    Answer

    Once the result has been used to pick or change a rule, that data has helped choose it. It is tuning data from then on, and no longer new.

Try it yourself

Run the script. Every number in this lesson comes from one Python script (below). It builds the simulated markets, tunes the four rules and runs them on both stretches. Change the conditions, the split between tuning years and new years, or the number of markets, and see what moves.

By hand, in a spreadsheet. Put the weekday in column A and each day's result in column B, one row a day, for two stretches of days. A pivot table (or =SUMIF(A:A, "Monday", B:B) for each weekday) gives each weekday's total on the first stretch. Mark the weekdays whose total is above zero: those are the tuned settings. Then add up the same weekdays on the second stretch and take the tuning gap: the first total minus the second.

The script needs simmarket.py, the course's simulated market, in the same folder.

The code behind every number (Python, on the course's simulated market; market seeds 20261109 and 2026111009 to 2026112008)
Loading…

Going further: nothing real at all, and a lucky fifth rule

With the reading's effect switched off. The script also builds every market again with the one real effect removed, so that no condition means anything. On the tuning years the four rules still averaged $36, $43, $66 and $173 over the 1,000 markets: the climb is almost the same with nothing to find. On the new years they averaged $38, $30, $24 and $20: down from the first condition on. A rising result on tuning data looks the same whether or not there is anything real underneath.

A fourth condition that means nothing. Add "was yesterday's close above the close before it?" and the rule has 240 settings, about 5.4 tuning days each. On Market 1's tuning years it made $201. On the new years it made $50, far more than the 120-setting rule's $16. That is luck, this time on the new years: over the 1,000 markets the 240-setting rule averaged $233 on the tuning years and $26 on the new ones, and it beat the 2-setting rule on the new years in only 134 markets. One result on new data is a check, not proof, in either direction.

For the preciseMarket 1's results in dollars and cents, tuning years then new years: no condition 24.0 and 27.2; 2 settings 37.2 and 54.6; 10 settings 65.4 and 36.2; 120 settings 149.5 and 15.9 (to one decimal). The tuned rules hold 1 of 2 kinds of day, 6 of 10 and 69 of 120. From 2 to 10 settings tuning changed three settings: Mondays after a high reading to stay out (−6.9 on the tuning years, +20.7 on the new), and Mondays and Wednesdays after a low reading to hold (+8.9 and +12.4 on the tuning years, +1.7 and +0.5 on the new). The tuning years hold 1,300 trading days and the new years 1,305. A day's result is rounded to the cent before anything is added, so every total is exact; whole-dollar figures round halves up. The planted effect adds 0.10% to a day's move per unit of the reading published the day before; the open-to-close part of the day carries 80% of it. 2 multiplied by itself 120 times is about 1.3 × 1036. Differences in the text are taken between the whole-dollar figures shown, so they can be a dollar away from the difference of the one-decimal figures: 150 − 65 = $85 in the text, 149.5 − 65.4 = 84.1 here; 37 − 55 = −$18 in the text, 37.2 − 54.6 = −17.4 here.

Honest notes

Where this comes from