Skip to content

Incubation & fill reconciliation

ExpertDuration 30 minTools Python 3, pandas, matplotlib, A demo or minimum-size live account

A walk-forward pass tells you a strategy might survive. Incubation tells you whether it survives your broker, your latency, and your real fills — the things a backtest can only estimate. This is the bridge between validation and live capital, and it is a formal stage, not a vibe. The deliverable is not a feeling that “it seems to be working”; it is a reconciled series that quantifies exactly how far live drifts from the backtest, so you can decide with numbers whether that drift is survivable.

Segment: 30:00–41:40 — Monte Carlo, incubation, and 'a backtest without slippage isn't realistic'watch full video

Watch for: Two moves: he shelves a finished system for 6–12 months and only goes live if the shadow results match the walk-forward numbers (~39:54), and he states plainly that a backtest without slippage and commission won't match real trading (~40:30).

Incubation is minimum-size reality-testing with the backtest running alongside. You take a strategy that has already passed walk-forward on net costs and you run it live — at the smallest size the broker allows, or on paper against live prices — while a copy of the backtest generates the same signals on the same data in parallel. The point is not to make money at this size; the point is to build two synchronized records, expected and actual, so you can subtract one from the other.

Why bother, when the backtest already modelled costs? Because the backtest modelled costs it assumed, and live trading delivers costs it imposes. Davey’s whole process hinges on this: he refuses to trade a freshly-built system, shelving it for six to twelve months and going live only if the shadow results reproduce the walk-forward numbers. A backtest, he says flatly, without slippage and commission is not realistic and will not match real trading. Incubation is where you find out how much it does not match — before the answer is expensive.

Fill reconciliation is the core measurement. For every trade the live system takes, you record two prices: the expected fill — the price your backtest assumed at that signal — and the actual fill — the price the broker gave you. The difference is that trade’s slippage. Do this across every entry and exit and you have a slippage series: not a single average, but a distribution. The distribution is the thing you read, because the shape tells you more than the mean.

What drift is normal, and what is alarming? Some drift is guaranteed and fine:

  • Normal: a modest, roughly symmetric slippage distribution centred near your backtest’s assumed cost. A few tenths of a pip either way on majors in liquid hours, occasionally more around news — this is the ordinary texture of real fills. It means your backtest’s cost model was honest and the strategy’s edge, if real, should carry over at size.
  • Alarming — systematically one-sided. If actual fills are consistently worse than expected — the distribution is shifted against you, not just spread around zero — the strategy’s edge may live entirely in the gap between assumed and real costs. That edge does not survive scale. Recall from Level 1 that most stop/stop-entry orders receive negative slippage, so a stop-heavy system will show a legitimately one-sided series; the question is whether it is worse than your model already assumed.
  • Alarming — drift over time. If slippage is fine in week one and steadily worsens, that is a signal in its own right — possibly a broker-model change, which Lesson 3.5 treats as a first-class monitoring input.
  • Alarming — divergence in signals, not just prices. If the live system and the parallel backtest sometimes take different trades (not just the same trade at a different price), you have a data or timing bug — a look-ahead the backtest exploited, or a bar-timestamp mismatch. Reconciliation catches this too, and it is more dangerous than slippage because it means your backtest was never testing the thing you are now trading.

Incubation ends with a verdict you can defend: the median and spread of live-minus-expected slippage, whether it is drifting, and whether the live signal series matches the backtest’s. If the drift is small and stable and the signals line up, you scale up with evidence. If not, you have caught it at minimum size — which is the entire point.

Build the expected-vs-actual fill logger and plot the slippage distribution. This is the schema you will feed for the whole incubation period.

import pandas as pd
import matplotlib.pyplot as plt
# One row per executed trade leg (entry or exit).
# expected_price = the price your parallel backtest assumed at this signal.
# actual_price = the fill the broker actually gave you (from history_deals_get).
fills = pd.DataFrame([
# symbol, side, signal_time, expected_price, actual_price, point
("EURUSD", "buy", "2026-07-01 08:00:00", 1.08120, 1.08123, 0.00001),
("EURUSD", "sell", "2026-07-01 10:30:00", 1.08210, 1.08206, 0.00001),
("EURUSD", "buy", "2026-07-02 08:00:00", 1.08050, 1.08061, 0.00001), # stop entry, worse
("EURUSD", "sell", "2026-07-02 14:00:00", 1.08300, 1.08299, 0.00001),
("EURUSD", "buy", "2026-07-03 08:00:00", 1.07990, 1.08004, 0.00001), # news, worse
], columns=["symbol", "side", "signal_time", "expected_price", "actual_price", "point"])
# Slippage in POINTS, signed so that negative = worse-than-expected for us.
def signed_slippage(row):
diff = row["actual_price"] - row["expected_price"]
# a buy filled higher than expected is bad; a sell filled lower than expected is bad
signed = -diff if row["side"] == "buy" else diff
return signed / row["point"]
fills["slippage_pts"] = fills.apply(signed_slippage, axis=1)
print(fills[["symbol", "side", "slippage_pts"]])
print("\nmedian:", round(fills["slippage_pts"].median(), 2),
"pts | mean:", round(fills["slippage_pts"].mean(), 2),
"pts | std:", round(fills["slippage_pts"].std(), 2), "pts")
# The distribution is the thing you read, not the average.
fills["slippage_pts"].plot(kind="hist", bins=12, edgecolor="black")
plt.axvline(0, color="red", linestyle="--", label="backtest assumption")
plt.axvline(fills["slippage_pts"].median(), color="blue", linestyle="-", label="live median")
plt.xlabel("slippage (points) | negative = worse than backtest assumed")
plt.title("Incubation fill reconciliation — slippage distribution")
plt.legend()
plt.tight_layout()
plt.savefig("slippage_distribution.png", dpi=120)
print("\nsaved slippage_distribution.png")

Read the output the way the explainer says: a distribution hugging zero and roughly symmetric is normal drift; a distribution shoved left (systematically worse) or sliding left over the weeks is the alarm.

Check yourself

  1. Incubation is best described as…

  2. Fill reconciliation compares…

  3. A modest, roughly symmetric slippage distribution centred near your backtest's cost assumption during incubation is…

  4. Why did Davey refuse to trade a system immediately after developing it?

You can move on when you can… run a minimum-size (or paper) system with the backtest in parallel, log expected against actual fills into a slippage series, plot its distribution, and tell normal drift (small, symmetric, stable) from alarming drift (one-sided, widening, or diverging signals) — and say why Davey shelves a system before trusting it live.

  • Kevin Davey, Building Winning Algorithmic Trading Systems — incubation as a named, formal stage between backtest and capital.
  • Darwinex on slippage: why and how much — the structural causes (latency, finite liquidity) behind the drift you are measuring.
  • FXCM published slippage statistics — real distributions to calibrate what “normal” looks like, including the negative skew on stop orders.