Quant from Scratch by QuantWright

Backtesting Essentials · Lesson 2

Look-ahead bias

Did the test peek at the future? Why a rule that sees what hasn't happened yet looks brilliant, and how to catch it.

Know the basics? Read the lesson notes

A rule, ten years, one click

Each day of prices is one bar. Its open is the day's first price, at 09:30, and its close the last, at 16:00. A backtest replays a trading rule over past bars to see what it would have made. As in the last lesson, it walks through the bars in order: at each one the rule decides from what is known so far, and the order fills at the next price available, usually the next bar's open (a next-bar fill).

The rule: on an up day, buy at the open and sell at the close. An up day is a day that closes higher than the day before. Each trade puts in $10,000, and there are no trading costs. Run it on ten years of a simulated market: prices made up by a program, not taken from any real market.

The up-day rule on Market 1, ten years of daily bars (simulated)

The account: $10,000 at the start (dotted line), plus every trade's gain or loss.

Trades0
Won–
Account$10,000

$10,000 became $94,665, a gain of $84,665. The rule traded 1,312 times and won 1,101 of them, 83.9%, and its line barely dips.

There was nothing to find

For this lesson the program behind Market 1 is set so that nothing before a day says which way that day will go. Each day's move is a fresh random draw (chance alone sets it, like a coin toss, with no link to which way earlier days went), with a slight upward drift (on average, prices creep up a little). In the morning, no rule can know how the day will end.

Yet this rule won 84 trades in 100. Where do you think its gains came from?

Keep your guess in mind. On the next screen you step through one week of the backtest.

Step through one week

Here is one week of the backtest, moment by moment. The grey curtain covers everything that hadn't happened yet. Each candle is one day: the box runs from the open to the close (hollow if the day closed above its open, filled if below), the thin line from the day's high to its low. On Monday morning, say which price the rule needed. Then press Step to move the clock on.

One week of Market 1, with a curtain over the future (simulated)

Dashed line: the previous day's close; a day that closes above it is an up day. ▶: the rule's buy at the open; a day without ▶ is one it skipped, because it "saw" the day would close down. Orange line: the time now (shown above it); the curtain hides everything after it.

Monday, 09:30: the rule says buy. Which price did it need to decide that?

Last Friday's close is one of the two it compares, and it was known. The other is Monday's close, which isn't known until 16:00. That one is behind the curtain: the peek.

The open is known at 09:30; it is the price the order fills at. To know whether Monday is an up day, the rule needs Monday's close, which isn't known until 16:00. That one is behind the curtain: the peek.

Right. An up day closes above the day before, so the rule needs Monday's close, which isn't known until 16:00. It is behind the curtain: the peek.

Now–
The rule–
It read–

Every morning, the rule decided with a price from 16:00. To know at 09:30 whether Monday is an up day, you need Monday's close, and nobody has it until the day is over. So the test bought only on days that were going to close up. That week it won all 3 of its trades.

This is look-ahead bias: a backtest that uses information which wasn't available at the moment of the decision. It explains the ten years, and luck can't: by luck alone, a rule like this would win about half its 1,312 trades, not 1,101. (It still lost some: an up day can close below its own open when it opened even higher.)

Fix it, then guess

The as-tested run broke the rule from the last lesson: it let a day's close decide a trade at that same day's open. The fix is to decide at the close, once the day is finished, and buy at the next open. The fixed rule: after an up day, buy at the next day's open and sell at that next day's close. Everything it reads is already known when it buys.

Over the same ten years, what will the fixed rule make?

Keep your guess in mind. On the next screen you switch between the two versions.

As tested, then fixed

The same market, the same ten years, the same $10,000 a trade. The only change is one day of waiting. Switch between the two.

The up-day rule as tested and fixed, Market 1 (simulated)

Coloured: the version you picked. Grey: the other one. Dotted line: the $10,000 start.

Version
Trades–
Won–
Made in ten years–

That small gain is no edge either (an edge is a real reason to make money on average). The market was built to drift up a little: on the average day it rose 0.018% from open to close. A rule that holds on 1,311 average days would make about 1,311 × 0.018% × $10,000 ≈ $2,400, and luck explains the rest. Nearly all of the $84,665 came from the peek.

Find the peek in three rules

That peek was plain once you stepped through it. Most are better hidden. Each rule below fills at the next price, so the leak must hide in the data. Bars can be any length: a 5-minute bar holds five minutes of prices. In a spreadsheet, each day is one row. An indicator is a number worked out from prices, such as an average; a 20-day average is the average of the last 20 closes. In each rule, tap the part that uses something not yet known at the moment of the decision.

Rule 1 · daily bars

Not this part. When the report comes out is a date on the calendar, known in advance.

Not this part. Buying is the action; the question is what the rule read to decide it.

The peek. Reports like this are often corrected in the weeks after they first come out, and many data files keep only the corrected number. On the morning itself, people saw the first number, which could be different. This leak is called revised data; the fix is the number as first published.

Not this part. Last month's report came out a month earlier, so it was known (as long as it is the number published then, not a later correction).

Rule 2 · 5-minute bars

Not this part. Buying at 10:00 is the action; the question is what the rule read to decide it.

The peek. At 10:00 the day still has six hours to go. Today's daily candle is finished at 16:00, and only then do you know its close and so whether it is hollow. This is an unfinished higher-timeframe bar: a longer bar used before it is complete. The fix is yesterday's daily candle, or today's price so far.

Not this part. The 5-minute bar that started at 09:55 finished at 10:00, so its rise is known.

Rule 3 · daily bars, in a spreadsheet

Not this part. Buying at the open is the action; the question is what the rule read to decide it.

Not this part. Yesterday's close was known since 16:00 yesterday.

The peek. The average in today's row includes today's close, which comes at 16:00. The rule reads it at the open. This is an indicator off by one row; the fix is the average from yesterday's row. A peek mixed in with older numbers is smaller: today's close is one of 20 averaged, so it moves the average only a little. A small peek still flatters the result, and looks more believable.

Peeks found0 of 3

Each is a common leak: revised data, a longer bar that isn't finished, and an indicator off by one row. With using the same bar's close (the up-day rule), these are the four to check first.

Scramble the future

You can't always read a rule's code. Here is a test that finds a peek without reading it. For each decision, scramble what came after it: swap that day's move from open to close for the move of another day chosen at random (one after the decision). Any such day will do: the rule couldn't have known its move at the time either, so an honest rule can't care which one it gets. The fixed rule's decision for a day reads only closes from before that day, so scrambling that day can't touch it. Then ask the rule again. A rule that reads only the past gives the same answer, because its past hasn't changed. A rule that peeks may change its mind.

40 days of decisions, before and after scrambling the future (simulated)

Filled square: buy. Empty square: don't buy. Squares show each day's original decision; a mark underneath means scrambling the future changed it. Both rows use the same 40 days; the fixed row is the as-tested row shifted one day later.

As tested: changed–
Fixed: changed–

Theory

Use only what was known: shift by one bar

At every moment, a backtest may use only what was known at that moment. Everything known up to a time t is called the information set at t, and a rule that reads anything outside it has look-ahead bias. For a rule that fills at a bar's open, the rule's answer must come from bars up to the one before. So the answer is shifted by one bar before it is traded:

positiont = signalt−1
t
the bar you trade; the order fills at its open
signalt−1
the rule's answer (1 = buy, 0 = don't), worked out at the close of bar t − 1 from bars up to t − 1 only
positiont
whether you hold during bar t (1 = hold, 0 = don't)
Worked example: the up-day rule

Its signal at a close is 1 when that close is above the one before. As tested, the backtest used positiont = signalt: Monday's position came from Monday's close, which arrives after Monday's 09:30 fill. Fixed, positiont = signalt−1: Monday's position comes from Friday's close, known since Friday at 16:00. One bar of shift took the ten years from $84,665 to $3,372.

In numbers: closes on days 1 to 5 of 10, 11, 10, 12, 13 give signals for days 2 to 5 of 1, 0, 1, 1 (11 is above 10, 10 isn't above 11, and so on). Shifted by one bar, those become the positions for days 3 to 6: 1, 0, 1, 1.

Holds when
  • every input carries the time it really became known: a report on the day it was published, not the end of the month it describes;
  • the fill comes after the decision (here, the next open).
Breaks when

The data's own times are wrong: for example, a 5-minute bar from 10:00 to 10:05 filed under 10:00, though its close is known only at 10:05 (lesson B4, Data quality). The future can also leak in through which markets you test (lesson B3, Survivorship bias).

Try it on new numbers. Closes on days 1 to 5 are 20, 21, 22, 21, 23. The fixed up-day rule takes its positions for days 3 to 6 from the signals of days 2 to 5. On which of days 3 to 6 does it not hold?

Signals for days 2 to 5: 1 (21 is above 20), 1 (22 above 21), 0 (21 isn't above 22), 1 (23 above 21). Shifted by one bar, the positions for days 3 to 6 are 1, 1, 0, 1, so it stays out on day 5.
Signals for days 2 to 5: 1 (21 is above 20), 1 (22 above 21), 0 (21 isn't above 22), 1 (23 above 21). Shifted by one bar, the positions for days 3 to 6 are 1, 1, 0, 1, so it stays out on day 5. Day 4 is where the as-tested rule would stay out: it uses day 4's own close.
Theory

Two tests for a leak

A rule's decision for bar t, made at its open, must not change when anything after that moment changes. Testing exactly that is the strongest check for look-ahead, a leakage test. You ran it by scrambling:

positiont(past, future) = positiont(past, future′)
positiont
the rule's decision for bar t, made at its open (1 = buy, 0 = don't): the decisions you scrambled
past
everything known at bar t's open
future, future′
what really happened after that moment, and a scrambled version of it

If the two sides differ at any t, the rule read the future.

Worked example: the up-day rule

As tested, the rule says buy if today's close is above yesterday's. Yesterday closed at 10 and today at 11, so its position for today is 1. Scramble today's move so that today closes at 9.5 instead, and the position becomes 0. The two sides differ: that is a peek. The fixed rule's position for today compares yesterday's close with the day before's; the scramble doesn't touch either, so its two sides always match.

A quicker warning sign: re-run the rule with every input one more bar late. A rule that peeks usually collapses: the as-tested rule, one bar late, is the fixed rule, and $84,665 fell to $3,372. A collapse alone isn't proof, because a real effect that lasts only one bar would fade too. The scramble test is the one that settles it for leaks in the data.

Holds when
  • you scramble every input the rule reads, including other files (reports, other markets, longer bars);
  • the rule gives the same answer every time it sees the same inputs (no random numbers of its own).
Breaks when

The peek isn't in the data: settings or markets chosen after seeing the results. Scrambling can't catch that (lesson B7, The best of many; lesson B10, The held-out test).

You check a friend's rule. Scrambling the future changes 0 of its 900 signals. With every input one bar later, its gain falls from $30,000 to $26,000. What do you conclude?

Its signal never changed when the future did, so the rule reads only the past. A drop from $30,000 to $26,000 is a fade, not a collapse like $84,665 to $3,372.
The scramble test is the strongest one, and it found nothing: 0 of 900 signals changed. A drop from $30,000 to $26,000 is a fade, not a collapse like $84,665 to $3,372.
The scramble test found no peek: 0 of 900 signals changed. But no peek is one problem fewer, not proof of an edge: luck and many tries can still explain $30,000 (lessons A5, The bell curve, and B7, The best of many).

What this means for your backtest

Four habits keep the future out of a test. Turn them over one at a time.

  1. Write down when each input becomes known.

    Next to every number a rule reads, note when it exists: a close at 16:00, a report on the day it is first published (with the number published that day), a daily candle at the end of the day. The fill must come after all of them.

Practice 1 of 4

A seller's backtest

The situationA website sells a trading signal. Its backtest on daily bars wins 81% of its trades over 15 years. The small print says: "Signals are calculated on each day's closing price. Trades are entered at that day's open."

What do you make of it?

A long test of a rule that peeks is still a test of a rule that peeks. The signal uses the day's close, which arrives at 16:00, to enter at that day's open, 09:30: the same peek as the up-day rule.
Costs would lower the result, but they don't remove the peek. The signal uses the day's close, which arrives at 16:00, to enter at that day's open, 09:30: the same peek as the up-day rule.
The signal uses the day's close, which arrives at 16:00, to enter at that day's open, 09:30: the same peek as the up-day rule, which won 84% on a market with nothing to find.
Fixed (signal at the close, entry at the next open), the result could be anything. Only the fixed test tells you.
Practice 2 of 4

A monthly report

The situationYour rule reads a monthly report on factory orders. Each month's report is published on the 20th of the following month: March's on 20 April, April's on 20 May, and so on. Your rule trades at the open on the 1st of each month.

For the trade on 1 June, which month's report is the latest one the rule may use? Type the month's number (January = 1).

Hint: a report becomes known when it is published, not when its month ends. Which reports were out by 1 June?

April's report came out on 20 May, before 1 June. May's comes out on 20 June, after the trade. The latest is April's: 4.
April's report came out on 20 May, before 1 June. May's comes out on 20 June, after the trade. The latest is April's: 4. A data file that puts May's number in the row for 31 May makes it look known on 31 May; that's the leak to avoid.
Practice 3 of 4

"It's only one day in 50"

The situationA friend's rule: buy at the open when the close is above its 50-day average, sell at the close. They took both numbers from today's row, so both include today's close. They say: "That's one day out of 50. It can't matter much."

Compared with the fixed version on the same data, what do you expect the as-tested backtest to show?

The rule doesn't only average today's close in with 49 others; it compares today's close itself with that average. Today's close decides most of what the trade earns, so the rule peeks at its own result.
It compares today's close itself with the average, so it peeks at its own result. The average is mostly 49 older closes, so the peek is smaller and looks less wild. On Market 1, this rule as tested won 56.9% of its trades and made $22,417; fixed, 51.4% and $547.
The as-tested version has no delay: it reads today's close, which helps it. It peeks at its own result, so it looks better, not worse.
A peek this size looks like a modest, believable edge, which makes it the dangerous kind.
Practice 4 of 4

A 20-day average in your spreadsheet

The situationYour spreadsheet rule buys at the open of day 101 when the last close is above its 20-day average.

With the shift done right, which 20 closes go into that average? Type the first day of the 20.

Hint: which is the last close known at day 101's open?

The last close known at day 101's open is day 100's. Twenty closes ending there start at day 100 − 20 + 1 = 81: days 81 to 100.
The last close known at day 101's open is day 100's. Twenty closes ending there start at day 100 − 20 + 1 = 81: days 81 to 100. Days 82 to 101 would include day 101's close, which comes after the open.
✓

Lesson done

A backtest may use only what was known at each moment. A rule that reads a price, a report or a bar before it exists looks brilliant and is worthless: on a market built with nothing to find, one bar of peeking turned $10,000 into $94,665. Shift every signal by one bar, note when each input becomes known, and scramble the future to check.

Key ideas

Look-ahead bias
a backtest that uses information which wasn't available at the moment of the decision
Information set
everything known up to a moment; a rule may read only from it
Shift by one bar
trade bar t on the signal from bar t − 1, so the fill comes after everything the rule read
The four common leaks
using the same bar's close (the up-day rule); revised data; a longer bar that isn't finished; an indicator off by one row
Revised data
numbers corrected after they first came out; a backtest should use them as first published
Leakage test
scramble everything after each decision; if any decision changes, the rule read the future

Formulas

positiont = signalt−1  ·  positiont(past, future) = positiont(past, future′)

Lesson notes

Look-ahead bias is a backtest using information that did not exist yet at the moment of the decision. The cure is to let each decision use only what was known then, and to check by changing the future and seeing whether any decision changes.

A rule that wins 84 trades in 100

Each day of prices is one bar, with an open (the first price, 09:30) and a close (the last, 16:00). A backtest replays a rule over past bars. The rule: on an up day, buy at the open and sell at the close, where an up day closes higher than the day before. Each trade puts in $10,000, with no costs. On ten years of a simulated market (prices made up by a program) it made 1,312 trades and won 1,101: a win rate (the share of trades that win) of 83.9%. $10,000 became $94,665, a gain of $84,665.

There was nothing to find

The market was built so that nothing before a day says which way it will go: each day's move is a fresh random draw, like a coin toss, with a slight upward drift (on average prices creep up a little). Half of the 1,312 trades, 656 (1,312 ÷ 2), is the coin-toss yardstick; the drift lifts it a little (the fixed rule below won 684 of 1,311). It is not 1,101.

The peek

The up-day rule uses a price that does not exist yet. On Monday at 09:30 it must decide whether to buy at the open, yet to know whether Monday is an up day it needs Monday's close, which does not exist until 16:00. In one simulated week it bought on 3 of the 5 days and won all 3 of those trades. This is look-ahead bias.

Fix the timing

The fix: after an up day, buy at the next day's open and sell at that day's close. Over the same ten years it made 1,311 trades, won 684 (52.2%) and gained $3,372, $2.57 a trade. One day of waiting took the gain from $84,665 to $3,372.

That is no edge either (a real reason to make money on average). The average day rose 0.018% from open to close, so 1,311 × 0.018% × $10,000 ≈ $2,400 ($2,372 with the average before rounding). Luck explains the rest, a gap of about $1,000.

Where peeks hide

An indicator is a number worked out from prices, such as an average. A report is known on the day it is published, not when its month ends. Three more leaks. Revised data: a jobs report is often corrected weeks later and many files keep only the corrected number; use the number as first published. An unfinished longer bar: at 10:00, today's daily candle (the day drawn as one bar) is not finished until 16:00; use yesterday's. An indicator off by one row: in a spreadsheet, the 20-day average (the average of the last 20 closes) in today's row includes today's close; use yesterday's row.

Scramble the future

You cannot always read a rule's code. Instead, take each day's decision (buy or not) and swap that day's move from open to close for the move of another day chosen at random, one after the decision. The rule could not have known that move then, so an honest rule cannot care which one it gets. Ask the rule again. A rule that reads only the past gives the same answer; one that peeks may change its mind. In the lesson's simulation, scrambling three times, the as-tested rule changed 1,090, 1,089 and 1,136 of its 2,606 decisions, one per day (41.8, 41.8 and 43.6%) and the fixed rule changed 0, 0 and 0. Not every decision, because the open is known at 09:30, and a scrambled day often still ends on the same side of yesterday's close.

Rerunning with every input one bar later also warns: a peeking rule usually collapses ($84,665 fell to $3,372), though a real one-bar effect would fade too. No peek found is one problem fewer, not proof of an edge.

Four habits

Note when each input becomes known, shift by one bar unless you can say why not, scramble the future for every new rule, and treat a result that looks too good as a bug report.

Formula: shift by one bar

A signal is the rule's answer at a bar's close (1 = buy, 0 = don't). Everything known up to a time t is the information set at t. A rule that fills at a bar's open may read only bars up to the one before, so its answer is shifted by one bar before it is traded.

positiont = signalt−1
t
the bar you trade; the order fills at its open
signalt−1
the rule's answer (1 = buy, 0 = don't), worked out at the close of bar t − 1 from bars up to t − 1 only
positiont
whether you hold during bar t (1 = hold, 0 = don't)
Worked example: the up-day rule

Its signal at a close is 1 when that close is above the one before. As tested, the backtest used positiont = signalt: Monday's position came from Monday's close, which arrives after Monday's 09:30 fill. Fixed, positiont = signalt−1: Monday's position comes from Friday's close, known since Friday at 16:00. One bar of shift took the ten years from $84,665 to $3,372.

In numbers: closes on days 1 to 5 of 10, 11, 10, 12, 13 give signals for days 2 to 5 of 1, 0, 1, 1 (11 is above 10, 10 isn't above 11, and so on). Shifted by one bar, those become the positions for days 3 to 6: 1, 0, 1, 1.

Holds whenBreaks when

The data's own times are wrong: for example, a 5-minute bar from 10:00 to 10:05 filed under 10:00, though its close is known only at 10:05 (lesson B4, Data quality). The future can also leak in through which markets you test (lesson B3, Survivorship bias).

Formula: the leakage test

A decision made at a bar's open must not change when anything after that moment changes. Testing exactly that, as the scramble does, is the leakage test.

positiont(past, future) = positiont(past, future′)
positiont
the rule's decision for bar t, made at its open (1 = buy, 0 = don't): the decisions scrambled above
past
everything known at bar t's open
future, future′
what really happened after that moment, and a scrambled version of it

If the two sides differ at any t, the rule read the future.

Worked example: the up-day rule

As tested, the rule says buy if today's close is above yesterday's. Yesterday closed at 10 and today at 11, so its position for today is 1. Scramble today's move so that today closes at 9.5 instead, and the position becomes 0. The two sides differ: that is a peek. The fixed rule's position for today compares yesterday's close with the day before's; the scramble doesn't touch either, so its two sides always match.

Holds whenBreaks when

The peek isn't in the data: settings or markets chosen after seeing the results. Scrambling can't catch that (lesson B7, The best of many; lesson B10, The held-out test).

One more worked example

Made-up numbers: a 3-day average

The rule: buy one share at the open when the close is above the average of the last 3 closes (today's included), and sell at that day's close. Closes on days 1 to 6: 30, 31, 29, 32, 33, 31. Opens on days 3 to 6: 30.5, 29.5, 32.5, 33.5.

Signals: day 3: (30 + 31 + 29) ÷ 3 = 30; 29 is not above it, so 0. Day 4: (31 + 29 + 32) ÷ 3 = 30.67; 32 is above, so 1. Day 5: (29 + 32 + 33) ÷ 3 = 31.33; 33 is above, so 1. Day 6: (32 + 33 + 31) ÷ 3 = 32; 31 is not above, so 0.

As tested (position = the same day's signal) it holds days 4 and 5: (32 − 29.5) + (33 − 32.5) = +3.0. Fixed (position = the signal a day earlier) it holds days 5 and 6: (33 − 32.5) + (31 − 33.5) = −2.0.

Common mistakes

Check yourself

  1. Closes on days 1 to 5 are 40, 42, 41, 43, 44. The fixed up-day rule takes its positions for days 3 to 6 from the signals of days 2 to 5. On which of days 3 to 6 does it not hold?
    AnswerSignals for days 2 to 5: 1 (42 is above 40), 0 (41 isn't above 42), 1 (43 above 41), 1 (44 above 43). Shifted by one bar, the positions for days 3 to 6 are 1, 0, 1, 1, so it does not hold on day 4.
  2. A website's backtest says: "Signals are calculated on each day's closing price. Trades are entered at that day's open." What is wrong, and what would a fixed test do?
    AnswerThe close arrives at 16:00, after the 09:30 open where the trade is entered, so the test reads the future: the up-day rule's peek. A fixed test works out the signal at the close and enters at the next open (positiont = signalt−1). Only the fixed test shows the result.
  3. A monthly report is published on the 15th of the following month. A rule trades at the open on 1 August. What is the latest month's report it may use?
    AnswerA report is known on the day it is published. June's came out on 15 July, before 1 August. July's comes out on 15 August, after the trade. The latest is June's, month 6.
  4. Scrambling the future changes 0 of a rule's 500 signals. With every input one bar later, its gain falls from $20,000 to $18,000. What do you conclude, and does it show an edge?
    AnswerNo peek found: no signal changed when the future did. A fall from $20,000 to $18,000 is a fade, not a collapse like the up-day rule's $84,665 to $3,372. It does not show an edge: no peek is one problem fewer, and luck and many tries can still explain a gain.
  5. A spreadsheet rule buys at the open of day 61 when the last close is above its 20-day average. Which 20 closes go into that average, if the shift is done right?
    AnswerThe last close known at day 61's open is day 60's, since a close arrives after the open. Twenty closes ending there start at day 60 − 20 + 1 = 41: days 41 to 60.

Try it yourself

Run the simulation. Every number in this lesson comes from one Python script (below) that uses the course's shared simulated market, SimMarket, with its fixed seed. Change the rule, the market or the shift and see what moves.

Make the leak by hand. In a spreadsheet, put each day's open in column A and close in column B. In C3 write =IF(B3>B2,1,0) (an up day) and copy it down. The leak: in D3, =C3*(B3/A3-1), the same row. The fix: =C2*(B3/A3-1), one row up. Sum column D both ways. Any prices will do, even made-up ones.

About QuantWright

QuantWright, which publishes this course, sells backtesting software. Its backtests use next-bar fills, the timing of this lesson's fixed rule, with commission and slippage. Through its Agent API, which connects AI assistants such as Claude Code, results also carry the look-ahead check. Neither replaces the habits above for code you write or read yourself. quantwright.com

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

The code behind every number (Python, SimMarket seed 20261109, scramble seed 2026110920)
Loading…

Going further: small peeks look believable

The up-day rule's peek was loud: 84% winning trades. Most peeks are quieter. Take the rule from practice question 3, "buy at the open when the close is above its N-day average", with both numbers from today's row. With N = 2 it is the up-day rule (a close above the average of itself and the day before is a close above the day before). As N grows, older closes dilute the peek.

The N-day average rule as tested and fixed, Market 1 (simulated)

Coloured: as tested, reading today's close. Grey: fixed, one day earlier.

Days in the average
As tested: won–
As tested: made–
Fixed: won–
Fixed: made–

At 50 days the rule as tested wins 56.9% of its trades and makes $22,417 in ten years. Nobody would call that impossible, which is the problem: a small peek looks like a modest, believable edge. Fixed, it wins 51.4% and makes $547. The leakage test catches both sizes alike, because it asks whether a decision changes, not how much the result improved.

For the preciseMarket: SimMarket Market 1 with the planted effect off, market(1, effect=False), seed 20261109, 2,607 trading days from 2015-01-05 to 2024-12-31. Trade: open to close of one day, $10,000 a trade, no costs, gains not reinvested; a win is a close strictly above the open. Up day: close strictly above the previous close. As tested, positiont = 1 if closet > closet−1; fixed, positiont = 1 if closet−1 > closet−2; one bar later still, closet−2 > closet−3. Results: as tested 1,312 trades, 1,101 wins (83.9%), +$84,665 ($64.53 a trade); fixed 1,311 trades, 684 wins (52.2%), +$3,372 ($2.57 a trade); one bar later still 1,310 trades, 50.7%, +$2,014. All 2,607 days: average open-to-close 0.018%, 51.4% close above their open; 1,311 × 0.018% × $10,000 = $2,372 with the unrounded average. N-day average, exact in cents (N × close against the sum of N closes), trading days t ≥ N: as tested won 83.9%, 72.3%, 61.8%, 56.9% and made $84,665, $64,205, $36,066, $22,417 for N = 2, 5, 20, 50; fixed 52.2%, 53.5%, 51.8%, 51.4% and $3,372, $7,026, $2,684, $547.
For the precise: the scrambleFor each decision day t ≥ 1, day t's open-to-close move is replaced by that of day j, drawn uniformly from days t … 2,606 (the first day of a shuffled future), with numpy default_rng(2026110920), three scrambles. The scrambled close is opent × closej ÷ openj. Decisions changed, as tested: 1,090, 1,089 and 1,136 of 2,606 (41.8%, 41.8%, 43.6%); fixed: 0 each time. Why not half: the open is known at 09:30, so a day that opened well above or below yesterday's close often stays on the same side whatever its scrambled move.

Honest notes

Where this comes from