Quant from Scratch by QuantWright

Backtesting Essentials · Lesson 1

Inside a backtest engine

Bars, signals, orders, fills: how a backtest walks through the past one day at a time, and where each order really fills.

Know the basics? Read the lesson notes

A day is a bar

This lesson runs on SimMarket, the course's simulated market: made-up prices, built to move the way market prices do, with no real market data in them. We use its Market 1, which starts at $100.

A backtest usually reads prices one day at a time, and sums each day up as a bar: its open (the day's first price), its high and low, and its close (the day's last price). Press Play to watch day 2 of Market 1 trade, five minutes at a time, from 09:30 to 16:00.

Market 1, day 2, five minutes at a time (simulated)

Left: the day so far, one small bar per five minutes (each five-minute stretch is drawn like a day, only smaller). Right: the day's bar as it grows. A bar is a line from the low to the high, with a mark to the left at the open and a mark to the right at the latest price.

Time09:30
Open–
High so far–
Low so far–
Latest price–

The day's high, 99.40, came in the five minutes from 11:20. At the time, nobody could know it would be the high: the price might still have gone higher. A bar is complete only at its close. At 16:00 all four prices are known: open 99.19, high 99.40, low 98.76, close 99.27.

A rule that reads one number

At each close, SimMarket also publishes one number for the day, which we call the reading. Day 2's reading was 1.23. It is invented for this course, not a real indicator; a later screen shows what it does to prices.

The ruleIf you hold nothing and today's reading is above 1, buy. If you hold, and today's reading is below 0, sell. One trade at a time.

The rule decides at each close, after reading that day's number. A decision to buy or sell is called a signal. Here are days 1 to 15. Tap every day the rule decides to buy. There are three.

Market 1's readings, days 1 to 15 (simulated)

Each tile is one day and the reading published at its close.

Days 2, 4 and 10. Days 5 and 11 also read above 1, but the rule was already holding a trade, so it waited. It decided to sell at the closes of days 3, 6 and 13, the first readings below 0 after each buy.

The rule says buy. At what price?

At day 2's close the rule reads 1.23 and decides to buy. To buy, it sends an order: an instruction to buy, which fills when someone actually trades with it, at some price.

DayOpenCloseReading
Day 299.1999.271.23
Day 399.1099.32−0.75

What is the first price a real buy order could get?

The close is the day's last trade. By the time the rule has read it, the closing auction has run, so no order can still get that price.
The close is the day's last trade, so the next price in the regular session is the next morning's open: 99.10.
The order doesn't have to wait for day 3 to finish. An order to buy at whatever the market price is fills at the first price available: day 3's open, 99.10.
Filling at the first price of the next bar is called a next-bar fill. Notice the night in between: day 2 closed at 99.27 and day 3 opened at 99.10. That jump from one close to the next open is the overnight gap.

The loop, one day at a time

A backtest engine is the program that does this for every day of the data. It is a loop: the same three moves, repeated each day. At the open, fill any order sent the night before. At the close, the day's bar is complete and its reading is out. Then decide, reading only the days so far, and send any order for the next open.

Press Step to move the engine through Market 1's first 30 days, one move at a time. The grey curtain covers the days the engine hasn't reached: it can't see them yet.

The engine on Market 1, days 1 to 30 (simulated)

Top: the loop, with the move it is on. Middle: one bar per day; orange ▲ a buy fill, black ▼ a sell fill; dashed: from the close where the rule decided (small circle) to the open where the order filled. Pink: days the rule holds a trade. Bottom bars: each day's reading, orange on a buy decision, black on a sell decision, with lines at 1 and 0.

Press Step to start day 1.

Day–
Holdingnothing
Waiting ordernone
TradeBoughtSoldResult
No trades yet

The first trade: the buy was decided at day 2's close and filled at day 3's open, 99.10. The sell was decided at day 3's close (reading −0.75) and filled at day 4's open, 99.39. With $10,000 in the trade, the result is 10,000 × (99.39 ÷ 99.10 − 1) = +$29.

Play to day 30 if you like: the engine makes the same three moves every day.

What the engine may read

The engine has reached the close of day 12 and the rule is about to decide. Which days' prices and readings may it use?

At day 12's close, everything up to and including that close is known. That is where the curtain stops.
Day 13's open hasn't happened yet at day 12's close. A rule that reads it is using the future, and its backtest can look better than anything you could really trade.
At its close, day 12 is complete: its open, high, low, close and reading are all known. That is why the rule decides there.
On day t, the rule reads days 1 to t. Everything after t stays behind the curtain.

Fill at the close, or at the next open?

Some backtests skip the wait. They fill each order at the very close the rule just read, as if the rule could read the close and trade at it in the same instant. Call that a same-close fill. The decisions don't change, only the prices. Switch between the two and watch the trade list.

The same rule, two ways to fill, days 1 to 30 (simulated)

Orange ▲ a buy fill, black ▼ a sell fill. With next-open fills, a dashed line runs from the close where the rule decided (small circle) to the open where the order filled. Pink: days the rule holds a trade. Bottom bars: each day's reading, orange on a buy decision, black on a sell decision.

Fill at
TradeBoughtSoldResult
Trades–
Total of the results–

The same six trades on the same days. Only the fill prices moved, and every result changed: the first trade is +$5 with same-close fills and +$29 with next-open fills. Over these 30 days the next-open fills came out ahead: $294 against $180.

Ten years

Market 1 runs for 2,607 trading days, about ten years, and the rule makes 333 trades with either fill. Give it an account that starts at $10,000 and adds each trade's result, with $10,000 going into every trade. Which fill will end with more in the account?

Keep this guess: the first 30 days pointed one way. On the next screen you run both for ten years.

Run the whole ten years

The same engine, the same 333 decisions, two fill settings. Press Run.

The account after each trade, Market 1, ten years (simulated)

Grey: same-close fills. Coloured: next-open fills. Dashed: the $10,000 start.

Same close–
Next open–
Difference–

The first 30 days said the opposite. Six trades are too few: over a handful of nights, luck decides which fill comes out ahead. Over 333 trades, something else shows. The next screen finds it.

Where the extra came from

We built SimMarket with one planted effect, a small pattern put in on purpose so the course has something to find: a high reading nudges the next day's price up a little, and a low reading nudges it down. Part of each day's move happens overnight, before the next open, and part of the nudge lands overnight too, by design.

So the nights after the rule's decisions lean its way. A same-close fill buys before the night that follows a high reading, and sells before the night that follows a low one. An order sent after the close catches neither night: it fills at the next open, after the night has happened. The daytime part of the nudge is collected by both fills.

To check that the nudge is the reason, switch it off. SimMarket can rebuild Market 1 with exactly the same ups and downs of luck and only the nudge removed. The rule makes the same decisions, because the readings are the same.

The account after each trade, with and without the nudge (simulated)

Grey: same-close fills. Coloured: next-open fills. Dashed: the $10,000 start.

Planted nudge
Same close–
Next open–
Difference–

Without the nudge, the difference shrinks from $2,209 to $620, and what's left is about what luck alone can give here. On SimMarket's other ordinary markets with no nudge, the gap came out at about $1,800 on two of them, one each way. Both lines also end lower, as the rule then only collects SimMarket's slow upward drift.

So a same-close fill flatters a rule when what the rule reads really does move the price: part of the move happens before any order can trade at a close you have only just read.

Theory

The backtest loop

What you stepped through is called an event-driven backtest: the engine replays the data one bar at a time, in order, and on each bar the rule may use only what was known by then. Using anything from later is called look-ahead (lesson B2). The loop, in plain steps:

for each bar t = 1, 2, … up to the last bar:
  open:   fill the order sent on bar t − 1 at Ot
  close:  bar t is complete
  decide: the rule reads bars 1 … t, nothing later
          if it trades, send an order for Ot+1
Pfill = Ot+1
t
the bar on which the rule decides; its close is the last price the rule has read
Ot+1
the open of the next bar: the first price after the decision (Ot is bar t's open, Ct its close)
Pfill
the price the order fills at: a next-bar fill. A same-close fill would use Ct, the close the rule just read
Worked example: your first trade

The buy was decided on bar t = 2, so it filled at O3 = 99.10. The sell was decided on bar 3, so it filled at O4 = 99.39. Result: 10,000 × (99.39 ÷ 99.10 − 1) = +$29.

Holds when
  • the order is small enough to fill at the open without moving the price;
  • the open is a price you could trade at (SimMarket's prices sit halfway between the price to buy and the price to sell, so a real buy pays a little more: lesson A12, Markets and prices);
  • trading costs are added on top (lesson A16, Costs).
Breaks when

The rule reads something dated after t, such as a number only published later (lesson B2, Look-ahead bias), or the data itself is wrong on some bar (lesson B4, Data quality).

Try it on new numbers. A rule reads Thursday's bar and decides to buy. Thursday opened at $40.00 and closed at $41.20. Friday opened at $41.50 and closed at $40.90. At what price does a next-bar fill buy?

Decided on Thursday, bar t, so Pfill = Ot+1: Friday's open, $41.50.
Decided on Thursday, bar t, so Pfill = Ot+1: Friday's open, $41.50. $41.20 is the close the rule read (a same-close fill), and $40.90 comes after the order has already filled.
Theory

What a same-close fill assumes

A same-close fill assumes the rule read the close and traded at it in the same instant. The difference between the price when you decide and the price you really get is called implementation shortfall; for one trade here it is rsame − rnext. In this lesson, with no costs, all of it comes from the overnight gap, g = Ot+1 ÷ Ct − 1: the move from the close the rule read to the next open; in real trading, costs and the bid-ask spread add to it. One trade has two gaps, one after each decision. Since Obuy = Cbuy × (1 + gbuy) and Osell = Csell × (1 + gsell), the same-close return is about the next-open return plus gbuy − gsell, and that difference is the shortfall:

rsame ≈ rnext + gbuy − gsell
rsame
the trade's result in %, with same-close fills
rnext
the same trade's result in %, with next-open fills
gbuy
the overnight gap after the buy decision, in %
gsell
the overnight gap after the sell decision, in %
≈
roughly equal: percentage moves multiply rather than add, but for moves of a few percent the difference is tiny

The same-close version comes out ahead when the night after a buy tends to rise (gbuy above 0) and the night after a sell tends to fall (gsell below 0). That happens when what the rule reads moves the price before any order can trade at a close you have only just read. When the reading tells nothing, the gaps are luck and average out.

Worked example: your first trade, then all 333

gbuy = 99.10 ÷ 99.27 − 1 = −0.17%, gsell = 99.39 ÷ 99.32 − 1 = +0.07%, and rnext = 99.39 ÷ 99.10 − 1 = 0.29%. So rsame ≈ 0.29 + (−0.17) − (+0.07) = 0.05%, the +$5 that same-close fills reported. On this trade the nights went against the same-close version.

Over all 333 trades they leaned its way: gbuy averaged +0.049% and gsell −0.018%, so each trade gained about gbuy − gsell = 0.049 − (−0.018) = 0.067%, which is $6.70 on $10,000. Times 333 trades, that is about $2,230, close to the $2,209 you saw.

Holds when
  • each order is a market order (one that takes whatever price is there, lesson A13) and fills at the next open, with no further delay;
  • the moves are a few percent at most (the ≈).
Breaks when

Orders take longer than a bar to fill, or are big enough to move the price themselves (lesson D15, Liquidity and market impact).

Try it on new numbers. Same-close fills report a trade at +3.0%. The night after the buy decision the price rose 0.8%; the night after the sell decision it fell 0.5%. About what would next-open fills report, in %?

Turn the formula round: rnext ≈ rsame − gbuy + gsell = 3.0 − 0.8 + (−0.5) = 1.7%. The same-close version banked both nights.
Turn the formula round: rnext ≈ rsame − gbuy + gsell = 3.0 − 0.8 + (−0.5) = 1.7%. The rise after the buy is missed by the next-open buy, and the fall after the sell is suffered by the next-open sell.

What this means for your backtest

Every backtest you run or read has a loop like this inside. Three questions follow from it. Turn them over one at a time.

  1. Where does each order fill?

    Find the line that sets the fill price. If it fills at the close the rule just read, the backtest assumes you traded the instant the bar ended. Run it again with next-bar fills and compare.

Practice 1 of 3

A website's backtest

The situationA trading website shows a backtest of its rule: buy when a day closes more than 2% up, sell at the close two days later. It reports 380 trades over ten years. The small print says orders fill "at the close that triggers them".

What is the most useful thing to ask?

The win count comes from the same fills. If every trade fills at a close that was gone by the time the rule had read it, the wins carry the same head start.
More years help against luck, but every trade would still fill at a close it had only just read. Ask about the fills first.
The rule reads the close and buys at it in the same instant, but no order can trade at a close you have only just read. Filled at the next open, the result is much closer to one you could have traded.
If what the rule reads (here, a big up day) tends to carry on overnight, the same-close version pockets that night and a real trader doesn't.
Practice 2 of 3

Two fills, one trade

The situationYour own backtest puts $10,000 into each trade. The rule decides to buy at Wednesday's close, $80.00, and Thursday opens at $81.20. It decides to sell at Friday's close, $84.00, and Monday opens at $83.10.

What is the trade's result with next-bar fills? Type it in dollars.

Hint: next-bar fills buy at the open after the buy decision and sell at the open after the sell decision. Then the result is 10,000 × (sell price ÷ buy price − 1).

Buy at Thursday's open, $81.20; sell at Monday's open, $83.10: 10,000 × (83.10 ÷ 81.20 − 1) = 10,000 × 0.0234 = $234.
Buy at Thursday's open, $81.20; sell at Monday's open, $83.10: 10,000 × (83.10 ÷ 81.20 − 1) = 10,000 × 0.0234 = $234.
Same-close fills would show 10,000 × (84.00 ÷ 80.00 − 1) = $500, more than twice as much, from the nights (and the weekend) in between: no order can trade at a close you have only just read.
Practice 3 of 3

Check the loop

The situationA friend shows you the heart of their engine. As in SimMarket, each day's reading comes out at that day's close. (In code, reading[t] means day t's reading, and open[t] means day t's open.)
for t in range(len(days) - 1):
    if reading[t] > 1 and not holding:
        buy(open[t])

What is wrong with it?

It reads day t's reading, which comes out at day t's close, then buys at day t's open, earlier that same morning. The order fills before the information existed.
Day t's reading comes out at the close, and open[t] was that morning. Buying there uses the future: a look-ahead. A next-bar fill would be buy(open[t + 1]).
close[t] would be a same-close fill: better than open[t], but still a price that is gone by the time the reading is out. Use open[t + 1].
✓

Lesson done

A backtest engine is a loop. Each day it fills any waiting order at the open, lets the rule read every day up to the close and nothing later, and sends any new order for the next open. Filling at the close the rule just read assumes you traded in the same instant you decided, and when what the rule reads really moves the price, that makes the result look better than anything you could have traded.

Key ideas

Bar
one period's prices: open, high, low and close; complete only at its close
Signal
the rule's decision to buy or sell, made at a bar's close
Order, fill
the instruction to trade, and the price it actually trades at
Event-driven backtest
an engine that replays the data bar by bar; on bar t the rule reads bars 1 to t only
Look-ahead
a rule using anything from after bar t (lesson B2)
Next-bar fill
the order fills at the next bar's open, the first price after the decision
Same-close fill
the order fills at the close the rule just read; optimistic
Overnight gap
the move from one close to the next open
Implementation shortfall
the difference between the price when you decide and the price you really get

Formulas

Pfill = Ot+1  ·  g = Ot+1 ÷ Ct − 1  ·  rsame ≈ rnext + gbuy − gsell

Lesson notes

A backtest runs a trading rule over past prices one day at a time. On each day the rule may use only what was known by that day's close. An order it sends (an instruction to buy or sell) fills, meaning it really trades, at the next price the rule could get: the next day's open. Filling at the close the rule has just read assumes a trade nobody could make. It flatters the result when what the rule reads moves the price the way the rule trades (up after it buys, down after it sells); when the reading tells nothing, the overnight gaps are luck and can leave either fill ahead.

Bars, readings, signals

The lesson runs on SimMarket, the course's source of simulated, made-up prices. It builds several price series, each called a market; Market 1, used here, starts at $100; the lesson also checks two more of its markets. A backtest usually sums a day up as a bar: its open (the day's first price), high, low and close (the last price). Day 2 of Market 1 opened at 99.19, ranged from 98.76 to 99.40 and closed at 99.27. Day 3 opened at 99.10 and closed at 99.32, and day 4 opened at 99.39. A bar is complete only at its close: when the high was set, nobody could know the price would not go higher.

At each close SimMarket also publishes a number, the reading, invented for the course and not a real indicator. The rule: if you hold nothing and the reading is above 1, buy; if you hold and it is below 0, sell; one trade at a time. A decision to buy or sell is a signal. The readings are part of the simulated market and are not listed here; what matters is the days they trigger. In days 1 to 15 the rule decides to buy at the closes of days 2, 4 and 10. Days 5 and 11 also read above 1, but it was already holding. It sells at the closes of days 3, 6 and 13.

Where the order fills, and the loop

An order fills when someone actually trades with it. The close is the day's last trade, so by the time the rule has read it that price is gone. The first price a real buy could get is the next open: day 3's 99.10, not day 2's close of 99.27. That is a next-bar fill, and the jump from one close to the next open is the overnight gap.

The engine repeats three moves each day. At the open, fill any order sent the night before. At the close, the bar is complete. Then decide, reading days 1 to t and nothing later (at day 12's close, not day 13's open, which is the future), and send any order for the next open. The first trade: bought at day 3's open, 99.10; sold at day 4's open, 99.39. With $10,000 in the trade the result is 10,000 × (99.39 ÷ 99.10 − 1) = +$29.26, about +$29.

Same close or next open?

A same-close fill fills each order at the close the rule just read, as if it could trade at it in the same instant. The decisions don't change, only the prices. In the lesson's days 1 to 30, the same six trades (each a buy and its later sale) give $180.48 with same-close fills and $294.40 with next-open fills, about $180 and $294. Over its ten simulated years (2,607 trading days) the rule makes 333 trades either way. With a $10,000 account, $10,000 in each trade and the account adding each result, same-close fills end at $20,927.87 and next-open fills at $18,718.66: $2,209.21 apart, about $2,209, the other way round from the 30 days. Six trades are too few: over a handful of nights luck decides.

Where the extra came from

SimMarket has one planted effect, a small pattern put in on purpose: a high reading nudges the next day's price up a little, a low one nudges it down, partly overnight. A same-close fill buys before the night after a high reading and sells before the night after a low one. An order sent after the close fills after the night. With only the nudge removed (same luck), the difference shrinks from $2,209.21 to $620.40, about $620. The lesson calls what is left luck: on two more of SimMarket's markets, also run with the nudge removed, the same-close and next-open results differed by about $1,800 on each, with same-close ahead on one and behind on the other, so with no nudge the sign is luck and can flip. This is one market and one comparison. A same-close fill flatters a rule when what it reads moves the price the way the rule trades. The reading and nudge are invented: nothing here says a real number behaves like this.

Formula: the next-bar fill

Pfill = Ot+1
t
the bar on which the rule decides; its close is the last price the rule has read
Ot+1
the open of the next bar: the first price after the decision (Ot is bar t's open, Ct its close)
Pfill
the price the order fills at: a next-bar fill. A same-close fill would use Ct, the close the rule just read
Worked example: the lesson's first trade

The buy was decided on bar t = 2, so it filled at O3 = 99.10. The sell was decided on bar 3, so it filled at O4 = 99.39. Result: 10,000 × (99.39 ÷ 99.10 − 1) = +$29.

Holds whenBreaks when

The rule reads something dated after t, such as a number only published later (lesson B2, Look-ahead bias), or the data itself is wrong on some bar (lesson B4, Data quality).

Formula: what a same-close fill assumes

The gap between the price when you decide and the price you really get is the implementation shortfall: here rsame − rnext. With no costs it all comes from the two overnight gaps of a trade, g = Ot+1 ÷ Ct − 1. Since O = C × (1 + g) at both ends:

rsame ≈ rnext + gbuy − gsell
rsame
the trade's result in %, with same-close fills
rnext
the same trade's result in %, with next-open fills
gbuy
the overnight gap after the buy decision, in %
gsell
the overnight gap after the sell decision, in %
≈
roughly equal: percentage moves multiply rather than add, but for moves of a few percent the difference is tiny
Worked example: the lesson's first trade, then all 333

gbuy = 99.10 ÷ 99.27 − 1 = −0.17%, gsell = 99.39 ÷ 99.32 − 1 = +0.07%, and rnext = 99.39 ÷ 99.10 − 1 = 0.29%. So rsame ≈ 0.29 + (−0.17) − (+0.07) = 0.05%, the +$5 that same-close fills reported. On this trade the nights went against the same-close version.

Over all 333 trades they leaned its way: gbuy averaged +0.049% and gsell −0.018%, so each trade gained about gbuy − gsell = 0.049 − (−0.018) = 0.067%, which is $6.70 on $10,000. Times 333 trades, that is about $2,230, close to the $2,209 difference in the ten-year run.

Holds whenBreaks when

Orders take longer than a bar to fill, or are big enough to move the price themselves (lesson D15, Liquidity and market impact).

One more worked example: a new trade

Worked example: a trade the lesson did not use

The rule decides to buy at Tuesday's close, $50.00. Wednesday opens at $50.40. It decides to sell at Thursday's close, $52.00, and Friday opens at $51.90. With $10,000 in the trade, next-bar fills give 10,000 × (51.90 ÷ 50.40 − 1) = $297.62, about $298, so rnext = 2.98%. Same-close fills give 10,000 × (52.00 ÷ 50.00 − 1) = $400, so rsame = 4.0%.

The gaps: gbuy = 50.40 ÷ 50.00 − 1 = +0.8%, gsell = 51.90 ÷ 52.00 − 1 = −0.19%. The formula gives 2.98 + 0.8 − (−0.19) = 3.97%, close to 4.0% (the ≈). Same-close fills report $102 more (400 − 297.62 = 102.38).

Common mistakes

Check yourself

  1. A rule decides to buy at Monday's close, $60.00. Tuesday opens at $60.30 and closes at $60.80. At what price does a next-bar fill buy, and what is the overnight gap?
    AnswerTuesday's open, $60.30: the fill is the open after the decision. The gap is g = 60.30 ÷ 60.00 − 1 = +0.5%.
  2. Same-close fills report a trade at +2.0%. The night after the buy decision the price rose 0.5%, and the night after the sell decision it rose 0.3%. About what do next-open fills report?
    AnswerTurning rsame ≈ rnext + gbuy − gsell round: rnext ≈ 2.0 − 0.5 + 0.3 = 1.8%.
  3. A website's backtest fills "at the close that triggers" each trade. What should you ask, and why?
    AnswerAsk what it reports with next-open fills. No order can trade at a close the rule has only just read, and if what the rule reads moves the price overnight, the same-close version collects that night.
  4. In the lesson's simulation, the first 30 days favoured next-open fills and the ten years favoured same-close fills. Why trust the ten years more?
    AnswerThe 30 days held six trades, too few: over a handful of nights luck decides which fill is ahead. The ten years held 333 trades, and rebuilding the market with the same luck and only the nudge removed cut the difference from $2,209 to $620, so the nudge made most of it. What is left is luck, and its sign can flip.

Try it yourself

Run the engine. Every number in this lesson comes from one Python script (below). It imports SimMarket (course/sim/simmarket.py, seed 20261109) and runs the loop in the function backtest, about twenty lines. Change the fill setting, the thresholds or the market and see what moves. The page runs the same loop in JavaScript and checks its numbers against the script's.

Run it by hand. In a spreadsheet, put each day on a row with its open, close and reading. Add a column for the decision (buy, sell or nothing), worked out from that row and the rows above only. The fill price is the next row's open. A same-close version takes the same row's close instead; compare the two totals.

About QuantWright

QuantWright, which publishes this course, sells backtesting software. Its backtests charge commission and slippage and use next-bar fills: an order decided on one bar fills on the next. Every result shows its code, data window, data source and costs, so you can check it. 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)
Loading…

Going further: the same-close fill hiding in one line

Many backtests don't loop at all. They work on whole columns at once (a vectorised backtest): one column of daily returns, one column of positions, multiplied together. In Python with pandas the usual line is:

pnl = position.shift(1) * close.pct_change()

The shift moves each day's decision one row down, so the decision made at day t's close earns the move from close t to close t + 1. That has no look-ahead, but it is a same-close fill: it earns the overnight gap the rule could not have traded. A next-open version needs the open-to-open move instead, from Ot+1 to Ot+2. Forget the shift altogether and the decision at day t's close earns the move that ended at that same close: full look-ahead, the subject of the next lesson. Three versions of one line, three different backtests. The loop makes the difference visible; the one-liner hides it.

For the preciseExactly, in log returns: ln(1 + rsame) = ln(1 + rnext) + ln(1 + gbuy) − ln(1 + gsell), since Csell ÷ Cbuy = (Osell+1 ÷ Obuy+1) × (1 + gbuy) ÷ (1 + gsell). SimMarket's design: the reading s adds 0.10% × s to the next day's close-to-close move, spread over the night and the day in proportion to their variance, and the night carries 20% of a day's variance. So the night after a reading s carries 0.2 × (0.03% + 0.10% × s) on average (0.03% a day is SimMarket's built-in rise). For this rule's buy decisions (readings above 1, about 1.52 on average for a standard normal) that is about +0.037%, and for its sell decisions (readings below 0, about −0.80) about −0.010%: about 0.047% a trade, or about $1,550 over 333 trades on $10,000 each. The measured gaps, +0.049% and −0.018%, include luck: with the nudge removed they are +0.018% and −0.001%.
For the precise: every numberMarket 1 (SimMarket seed 20261109), 2,607 trading days, day numbers counted from 1 on the page. Rule: buy when holding nothing and the reading > 1; sell when holding and the reading < 0; $10,000 a trade; no costs; account = $10,000 + the sum of the results (no compounding). A decision on the last day never fills, and a trade still open at the end is not counted. Days 1 to 30: same-close total $180.48, next-open total $294.40, six trades each. Ten years, nudge on: 333 trades either way; same close ends at $20,927.87 (201 trades won), next open at $18,718.66 (197 won); difference $2,209.21. Nudge removed (market(1, effect=False), every random draw the same): $13,111.02 against $12,490.62; difference $620.40. The same check with the nudge removed on SimMarket's other ordinary markets (same close minus next open): Market 2 −$1,762.69, Market 4 +$1,789.09, Market 5 −$83.39. In SimMarket's code the reading is called signal.

Honest notes

Where this comes from