Quant from Scratch by QuantWright

Backtesting Essentials · Lesson 15

A research workflow

Write the hypothesis and the test before the first run, log every version, and let one held-out run give the verdict. The order keeps your own choices from deciding the result.

Know the basics? Read the lesson notes

From a question to a hypothesis

Research starts with a question. Here it is about a simulated market that publishes a made-up number each evening, the reading. Does the reading say which way the next day goes?

In half the simulated markets of this lesson it does: the next day's move gets an extra 0.10% for each unit of the reading (the reading usually lands between −2 and +2; a reading of +1 adds 0.10%, and a typical day moves about 1.2%). In the other half the reading moves nothing. You will work on one market without being told which kind it is.

A question can't turn out wrong. A hypothesis can: it names the rule, the data, a fair yardstick (something only a real effect would beat; the next card makes it exact) and the result that would count against it. Which of these is a hypothesis?

Any result fits "something useful": a gain, a loss, a pattern on Tuesdays. Nothing could count against it, so nothing can test it. B names a rule, a yardstick and a result that would make it wrong.
It names the days, a yardstick and the result that would make it wrong. The next cards make each part exact.
"Some version" can always be found by trying more versions, so no result counts against it. B names a rule, a yardstick and a result that would make it wrong.

Written in full, before any run: holding the market for a day only after a reading above 0 beats a fair yardstick on years set aside; an error bar on those years that reaches $0 or below counts against it. An error bar is a range of plausible values for a result, worked out when the test runs. Without that last clause, any result can be read as support.

The exact rule, its costs and its yardstick

The rule: at each close, when the reading comes out, hold $100 until the next close if the reading is above 0; otherwise stay out. Each trade costs 2 cents, about the gap between the buying and selling price at the close in this market. Every result in this lesson is after costs. Written down before any run, the rule and its costs can't drift toward whatever worked.

The yardstick. These markets rise on average, so a rule that sits out some days trails just holding for that reason alone (the lesson Benchmarks). The hypothesis is about which way the next day goes, so the rule is compared with holding's share: just holding's result times the share of days the rule is in. The rule's result minus holding's share is its excess.

Over four years just holding made $50. A rule was in the market on 40 of every 100 days and made $26 after costs. What is its excess?

Holding's share: 0.40 × 50 = $20. Excess: 26 − 20 = $6.
First holding's share: the rule is in on 40 of 100 days, so 0.40 × 50 = $20. Then the excess: 26 − 20 = $6. Against the whole of just holding the rule would trail by $24, for no reason but the days it sat out.

The split and the line, before any run

Eight simulated years exist, 2015 to 2022. The plan splits them before the first run: working years, 2015–2018, where versions are tried and chosen, and held-out years, 2019–2022, kept for one run of one chosen version (the lesson The held-out test). The held-out years stay out of every choice.

The line: a finding only if the excess's error bar sits wholly above $0. The bar reaches 1.96 standard errors either side of the excess, a standard error being how much the excess would wobble from luck alone. A version with nothing in it, whose excess averages $0, clears that line about 2.5 times in 100 (the held-out card shows why).

The plan, written before any run

Plan Hypothesis: holding after a reading above 0 beats holding's share. Counts against it: an error bar that reaches $0.
Rule: in from the close when the reading is above 0. Costs: 2 cents a trade.
Working years: 2015–2018. Held-out years: 2019–2022, one run.
Line: error bar wholly above $0. The pick: the logged version with the most dollars of excess on the working years.

Once the plan is written, which of these may still change without spoiling the test?

A line moved after the results can be moved to fit them, and then the test tests nothing. Versions on the working years may change, if each is written down (next card).
Moving the years after looking at results lets the results choose the test. Versions on the working years may change, if each is written down (next card).
Trying versions is what the working years are for. Each one is written down, because each is another chance for luck to look like a finding (next card).

Every version goes in the log

On the working years you may try versions of the rule: the level the reading must clear (−0.5, 0, 0.5 or 1) and an extra condition (none, not on Mondays, not on Fridays, only after a down day, only after an up day). Four levels and five conditions make 20 versions. Every version you run goes in the version log, kept or not.

The more versions, the more chances that one clears the line by luck. The lesson The best of many gave that chance for n tries with nothing in them, each clearing the line with chance p: 1 − (1 − p)n. Each version's z is how many standard errors its excess sits above zero, so the line is z above 1.96. Run five versions of your choice and watch the chance grow.

Your version log, on the working years 2015–2018 (simulated, after costs)

Each dot: one version's z, in the order run. Dashed: the line, 1.96.

Level:
Extra:
Versions logged0
Chance at least one clears 1.96 by luck–
Last run–

With all 20 logged, the chance that at least one version with nothing in it clears 1.96 by luck is 1 − 0.97520 = 0.397, about 40 in 100, where 0.975 = 1 − 0.025 is the chance one version misses. On this market none did: the highest z was 1.57.

The plan's pick goes by dollars of excess, not by the highest z, because that is the rule the plan fixed in advance: version 17, readings above 1, not on Mondays, with $19 (z = 1.46). What the log tells you: even a pass from the best of 20 would have been fairly likely from luck alone, so the pick still faces its one held-out run.

For the preciseVersion 19 (readings above 1, only after a down day) had the highest z but less excess. Version 4 (readings above 0, only after a down day) also rounds to $19; in cents version 17 led, 1,930 cents against 1,852. The working years hold 1,039 trading days.

One run on the held-out years, and a verdict

The plan sends the pick to the held-out years once, settings unchanged. Will it clear the line?

The pick on 2019–2022: its excess, added up day by day (simulated, after costs)

Orange: the running excess. Bar at the end: its error bar. Dashed: $0.

Your guess:

Over the 1,044 held-out days the pick's excess was −$8. One day's excess swung by σ = $0.36, the typical size of one day's swing (its standard deviation). That is the swing of the excess, not of the price: about a third of a typical day's move of about $1.20, because on most days the excess is only a small share of the move. √1,044 = 32.3, so the standard error is 0.36 × 32.3 = 11.6 and the bar 1.96 × 11.6 = 22.7, about $23 either side: from −8 − 23 = −$31 to −8 + 23 = $15. The bar reaches $0, so the plan says: no finding. Why 1.96: a range that wide misses the excess the rule would average over endless days only 5 times in 100, 2.5 on each side, which is where the line's 2.5 in 100 comes from.

The verdict carries its uncertainty: "no finding; excess −$8 ± $23 after costs on 2019–2022." It does not say the reading is useless. Here it was: this market had its effect switched off. That is one market, and the next card runs the plan on 1,000.

Two ways of working, on 1,000 markets

Picture two researchers on each of 1,000 simulated markets. Each market is built twice: with nothing to find, and with the small effect from the first card.

The planner follows the plan: logs all 20 versions on the working years and runs the pick once on the held-out years. The tweaker sets nothing aside: runs version 1 on all eight years, and if it misses the line, the next version, stopping at the first that clears it and reporting only that one.

With nothing to find, how many of the 1,000 tweakers end with a finding?

Findings in 1,000 research runs (simulated, after costs)

Your guess:

With nothing to find, 15 planners ended with a finding (2.5 in 100 would give about 25 in 1,000; 15 is this run's count) and 112 tweakers did, about seven to eight times as many.

With the small effect, the tweaker found it in 968 and the planner in 376. The tweaker had twice the data (all eight years against the planner's four held-out years) and kept trying until something passed, and both help it find a real effect. So it finds more real effects and about seven to eight times as many false ones, and from inside its own log it can't tell which of its findings is which. How many of them are luck depends on how common real effects are: here half the markets had one; if real effects were rare, many of the tweaker's findings would be luck. The planner's cost is missing a small real effect more often.

For the preciseEvery count is out of 1,000 of this simulation's markets, the same markets in both worlds (the same draws with the effect switched off or on). The reading is a standard normal draw. With nothing to find, a single version cleared the line on the held-out years in 302 of 20,000 version runs, and a tweaker who ended with a finding had run 10.3 versions on average. With the effect, the tweaker's first version cleared the line in 774 and the planner's held-out run missed it in 624.
Theory

The version log and the chance of a lucky finding

A version with nothing in it that clears the line anyway is a false positive (the lesson The best of many). Trying many versions and keeping the best is multiple testing. The version log makes the chance of a false positive countable: n is every version run, kept or dropped.

chance at least one clears = 1 − (1 − p)n
p
the chance one version with nothing in it clears the line: 0.025 for z above 1.96, when its excess averages $0
n
the number of versions in the log, every one run, kept or dropped
(1 − p)n
the chance all n miss: 1 − p multiplied by itself n times
Worked example: your log

n = 20, p = 0.025. One version misses with chance 0.975; all 20 miss with chance 0.97520 = 0.603; at least one clears with 1 − 0.603 = 0.397, about 40 in 100. So a pass from the best of this log is weak evidence until the held-out run.

Holds when
  • no version has anything in it (the formula counts lucky passes);
  • each clears the line with the same chance p;
  • the versions are separate tries: one's luck says nothing about another's;
  • n counts every version, kept or dropped.
Breaks when

The versions are alike: sharing most of their days, they are fewer than n separate tries, so the formula overstates the chance. Here the tweakers ended with a finding in 112 of 1,000, well under the formula's 397 for 20 separate tries at p = 0.025: their versions are related, and costs made each pass less often (the box below gives the numbers). Raising the line to match the log, for related versions too, is the topic of the lesson Many tests at once.

For the preciseWith nothing to find, one version cleared the line on the eight years in 208 of 20,000 runs, p ≈ 0.01 (under 0.025, because costs pull its excess below $0). For the tweakers' 20 versions the formula, with n = 20 and that p, gives 0.9920 = 0.82 and 1 − 0.82 = 0.18, or 180 tweakers in 1,000; the count was 112.

New numbers. A log of 10 versions, each with p = 0.025. What is the chance at least one clears the line by luck? Type a percentage.

0.97510 = 0.776, so at least one clears with 1 − 0.776 = 0.224: about 22 in 100, against 2.5 in 100 for one version.
One version misses with 1 − 0.025 = 0.975. All 10 miss: 0.975 multiplied by itself 10 times = 0.776. At least one clears: 1 − 0.776 = 0.224, about 22 in 100.

Spot the broken step

Years that helped choose a version are in-sample, and a result on them flatters it. Below are three made-up write-ups. Each breaks one step of the plan. Name it for each.

Workflow AWrote the plan. Tried 12 versions on the working years, all logged. Halfway through, ran the best so far on the held-out years "just to see", then tried 4 more and ran the final pick there once.
Workflow BWrote the plan. Tried 30 versions on the working years. The write-up lists the 3 that looked best and says "3 versions tested". The pick, run once on the held-out years, cleared the line.
Workflow CHypothesis: days after a high reading do better. The pick, run once on the held-out years: no finding. Days after a low reading did well there, so the write-up's hypothesis became "days after a low reading do better", with a finding.

A: the held-out years helped choose the final version, so they are in-sample and the last run flatters it. B: the write-up hides 27 of the 30 versions, so the chance of a lucky pass looks far smaller than it was. C: the held-out years chose the hypothesis, so the "finding" is a result on in-sample data.

What this means for your research

The steps, in order, each with what goes wrong without it.

  1. A question.

    Without one, you end up testing whatever looked good.

Practice 1 of 3

A friend's backtest

The situationA friend's best version cleared the lesson's line, z above 1.96, which a version with nothing in it clears 2.5 times in 100. Her log shows she ran 14 versions before this one stood out.

If none of the 14 had anything in them, what is the chance that at least one would clear that line by luck? Type a percentage.

Hint: 1 − (1 − p)n with p = 0.025 and n = 14. On a phone calculator: 0.975, the xy key, 14.

0.97514 = 0.702, so 1 − 0.702 = 0.298: about 30 in 100, if the versions were separate tries, against 2.5 in 100 for one version.
One version misses with 0.975. All 14 miss: 0.975 multiplied by itself 14 times = 0.702. At least one clears: 1 − 0.702 = 0.298, about 30 in 100.
Practice 2 of 3

Writing the verdict

The situationYour pick ran once, as planned, on 900 held-out days. Its excess was $45 after costs, and one day's excess had a standard deviation of $1.00.

How many dollars either side of $45 does the error bar reach?

Hint: error bar = excess ± 1.96 × σ × √n, and √900 = 30.

Standard error: 1.00 × 30 = $30. Bar: 1.96 × 30 = $58.80 either side, from −$13.80 to $103.80. Zero is inside, so the plan says no finding: "excess $45 ± $58.80 after costs".
√900 = 30, so the standard error is 1.00 × 30 = $30, and the bar 1.96 × 30 = $58.80 either side: from 45 − 58.80 = −$13.80 to 45 + 58.80 = $103.80. Zero is inside, so no finding.
Practice 3 of 3

A good-looking version

The situationYou logged 20 versions on your working years. The best has z = 2.5 there. Your held-out years are untouched.

What is your next step?

With 20 logged, about 40 in 100 logs of versions with nothing in them would hold one past 1.96, so a best-of-20 past the line is weak evidence. The one held-out run gives the verdict.
1.96 is the line for one version tested once. With 20 logged, about 40 in 100 logs of versions with nothing in them hold one past 1.96, so a best-of-20 past it is weak evidence. Run the pick once on the held-out years.
More versions only raise the chance that one looks good by luck. Run the pick once on the held-out years.
✓

Lesson done

A sound workflow: before any run, the question, a hypothesis with the result that would count against it, the exact rule, its costs and the data split are written down. Every version run on the working years is logged, since the count sets the chance that the best of them is luck. One chosen version runs once on the held-out years, and the verdict is written with its error bar. In this lesson's simulation, after costs, with nothing to find, 15 of 1,000 planners and 112 of 1,000 tweakers ended with a finding. With a small planted effect the tweaker found it more often, 968 against 376, with twice the data and by trying until something passed; it also had about seven to eight times as many false findings, and it can't tell which of its findings is which. How much that matters depends on how common real effects are. The planner's cost is missing a small effect more often.

Key ideas

Hypothesis
a statement a test can count against: the rule, the data, the yardstick and the result that would make it wrong, written before any run
Holding's share
just holding's result times the share of days a rule is in the market: a yardstick that takes out a rising market's average rise
Excess
a rule's result minus holding's share, over the same days
Version log
a record of every version run, kept or dropped; its count is the n in the chance of a lucky finding
Finding
a reported version that clears the line written in the plan

Formula

chance at least one of n clears = 1 − (1 − p)n

For versions with nothing in them, each clearing the line with chance p, as separate tries, with n counting every version run. Alike versions make it overstate the chance.

Lesson notes

A research workflow is the order of steps that makes it harder for a researcher to fool themselves. Before any run: a question, a hypothesis that names the result that would count against it, the exact rule, its costs and the split of the data into working years and held-out years. Then every version tried on the working years goes in a log, one chosen version runs once on the held-out years, and the verdict is written with its error bar (the result plus or minus 1.96 standard errors). All numbers are from the lesson's simulated markets and are after costs; they show what happens in this simulation, not in any real market.

The question, the hypothesis and the yardstick

The lesson's simulated markets publish a made-up number each evening, the reading. The question: does it say which way the next day goes? In half the simulated markets it does: the next day's move gets an extra 0.10% for each unit of the reading, against a typical day's move of about 1.2%. In the other half it moves nothing.

A question can't turn out wrong. A hypothesis can: it names the rule, the data, a fair yardstick and the result that would count against it. The lesson's: holding the market for a day only after a reading above 0 beats a fair yardstick after costs on years set aside, and an error bar on those years that reaches $0 or below counts against it. An error bar is a range of plausible values for a result: the result ± 1.96 standard errors, where a standard error is how much the result would wobble from luck alone. "The reading tells us something" is not a hypothesis, because no result could count against it.

The rule holds $100 from one close to the next when the reading is above 0. Each trade costs 2 cents. These markets rise on average, so a rule that sits out some days trails just holding for that reason alone. The hypothesis is about direction, so the yardstick is holding's share: just holding's result times the share of days the rule is in. The rule's result minus holding's share is its excess. A rule in on 40 of every 100 days, making $26 when just holding made $50, has an excess of 26 − 0.40 × 50 = $6.

The split and the line

Eight simulated years, 2015 to 2022, are split before the first run: working years 2015–2018 for trying versions, and held-out years 2019–2022 for one run of one chosen version. Years that helped choose a version are in-sample, and a result on them flatters it. The line: a finding only if the excess's error bar sits wholly above $0, which is the excess's z (how many standard errors it sits above zero) above 1.96. A version with nothing in it, whose excess averages $0, clears that line about 2.5 times in 100. Once the plan is written, the line and the years stay fixed; the versions tried on the working years may change, each one logged.

The version log

A version is one setting of the rule: here a level the reading must clear (−0.5, 0, 0.5 or 1) and an extra condition (none, not on Mondays, not on Fridays, only after a down day, only after an up day), 20 in all. Every version run goes in the version log. With 20 logged, the chance that at least one with nothing in it clears 1.96 by luck is 1 − 0.97520 = 0.397, about 40 in 100 (for separate tries; 0.975 = 1 − 0.025 is the chance one version misses). On the lesson's market no version cleared 1.96 on the working years (the highest z was 1.57). The pick goes by dollars of excess, not by the highest z, because that is the rule the plan fixed in advance: version 17 (readings above 1, not on Mondays), $19, z = 1.46.

One held-out run and the verdict

The pick ran once on the 1,044 held-out days. Its excess was −$8. One day's excess swung by σ = $0.36, the typical size of one day's swing (its standard deviation): the swing of the excess, not of the price, about a third of a typical day's move of about $1.20. √1,044 = 32.3, so the standard error was 0.36 × 32.3 = 11.6 and the error bar 1.96 × 11.6 = 22.7, about $23 either side: from −$31 to $15. Zero is inside, so the plan says no finding, and the verdict reads "excess −$8 ± $23 after costs". That market had its effect switched off; it is one market.

Two ways of working, on 1,000 markets

On each of 1,000 simulated markets, each built with nothing to find and again with the small effect, two researchers worked. The planner logged all 20 versions on the working years and ran the pick once on the held-out years. The tweaker set nothing aside, ran version 1 on all eight years, then the next, stopping at the first that cleared the line and reporting only that one.

With nothing to find, 15 planners and 112 tweakers ended with a finding: about seven to eight times as many. With the small effect, the tweaker found it in 968 and the planner in 376. The tweaker had twice the data (all eight years against the planner's four held-out years) and kept trying until something passed, and both help it find a real effect. So it finds more real effects and about seven to eight times as many false ones, and from inside its own log it can't tell which of its findings is which. How many of them are luck depends on how common real effects are: here half the markets had one; if real effects were rare, many of the tweaker's findings would be luck. The planner's cost is missing a small real effect more often.

Formula: the chance of a lucky finding

A version with nothing in it that clears the line anyway is a false positive; trying many versions and keeping the best is multiple testing. The version log makes the chance countable: n is every version run, kept or dropped.

chance at least one clears = 1 − (1 − p)n
p
the chance one version with nothing in it clears the line: 0.025 for z above 1.96, when its excess averages $0
n
the number of versions in the log, every one run, kept or dropped
(1 − p)n
the chance all n miss: 1 − p multiplied by itself n times
Worked example: the lesson's log

n = 20, p = 0.025: all 20 miss with 0.97520 = 0.603, so at least one clears with 1 − 0.603 = 0.397, about 40 in 100. A pass from the best of this log is weak evidence until the held-out run.

Holds whenBreaks when

The versions are alike. The lesson's share most of their days, so the formula overstates the chance. In the simulation one version with nothing in it cleared the line on the eight years about 1 time in 100 (p ≈ 0.01); with n = 20 the formula gives 1 − 0.9920 = 0.18, or 180 tweakers in 1,000, against the 112 counted. Raising the line to match the log is the topic of the lesson Many tests at once.

One more worked example

Made-up round numbers

A researcher logs 40 versions, each clearing the line with p = 0.025 if it has nothing in it. All 40 miss with 0.97540 = 0.363, so at least one clears by luck with 1 − 0.363 = 0.637, about 64 in 100, for separate tries. A write-up that says "4 versions tested" would suggest 1 − 0.9754 = 0.096, about 10 in 100.

Common mistakes

Check yourself

  1. A log holds 5 versions, each with p = 0.025. What is the chance that at least one clears the line by luck?
    Answer

    0.9755 = 0.881, so 1 − 0.881 = 0.119, about 12 in 100, if the versions have nothing in them and are separate tries.

  2. Why is the rule compared with holding's share and not with the whole of just holding?
    Answer

    The markets rise on average, so a rule that sits out some days trails just holding for that reason alone. The hypothesis is about which way the next day goes, so the yardstick takes out the share of the rise the rule could not have had: just holding's result times the share of days the rule is in.

  3. A pick's excess on the held-out years is −$8 with an error bar of $23 either side. Write the verdict.
    Answer

    "No finding: excess −$8 ± $23 after costs", from −$31 to $15. The bar reaches $0, so the hypothesis's own test counts against it. It does not prove the reading useless.

  4. In the lesson's simulation, with nothing to find, how many of 1,000 tweakers and how many of 1,000 planners ended with a finding? Why did the tweaker also find the real effect more often, and what does that habit cost?
    Answer

    112 tweakers and 15 planners, after costs. With the small effect the tweaker found it in 968 against 376, because it used twice the data and tried until something passed. It also had about seven to eight times as many false findings, and from inside its log it can't tell which finding is which; the planner's cost is missing a small effect more often.

  5. (from the lesson The held-out test) Why does a second run on the held-out years spoil them?
    Answer

    A second run, or a second version run there, lets the held-out years help choose the rule. They are then in-sample: years that helped choose a version, whose results flatter it.

Try it yourself

Run the script. Every number in this lesson comes from one Python script (below). It builds the simulated markets, runs the 20 versions on the working years, makes the pick, runs it once on the held-out years, and puts the planner and the tweaker through 1,000 markets in each world. Change the number of versions, the line or the length of the held-out years, and see what moves.

By hand, in a spreadsheet. Before the first run, write the plan in a sheet with today's date: the question, the hypothesis and what counts against it, the rule, the costs, the working and held-out dates, and the line. Make a second sheet, the log: one row per version run, with its date, its settings and its result, never deleted. The count of rows is n; =1-(1-0.025)^n is the chance a version with nothing in it clears 1.96 at least once, for separate tries.

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 generator; market seeds 2026151001 to 2026152001)
Loading…

Honest notes

Where this comes from