LibraryAI in quantitative finance

Look-ahead bias and data leakage in ML trading models, measured on four markets

Short answer. We trained one machine-learning model on US stocks, the NIFTY 50, EUR/USD and Bitcoin, and tested it five ways. Tested honestly, it called the next week's direction 52.7% of the time on average, about a coin flip, with a Sharpe ratio of 0.16. Three mistakes each made it look like a winner:

Education only, not advice. Past results, ours included, do not show what a strategy will do next.

-1 0 1 2 3 4 5 6 7 Sharpe ratio after costs Honest: walk-forward Leak: shuffled 5-fold Leak: centred average Honest: average rule, next day Leak: average rule, same day -1 0 1 2 3 4 5 6 7 Sharpe ratio after costs Honest: walk-forward Leak: shuffled 5-fold Leak: centred average Honest: average rule, next day Leak: average rule, same day
Figure 1. The bar is the average over four markets. Each circle is one market. Blue rows are honest tests and grey rows leak the future. All after costs, on the same dates.

What look-ahead bias and leakage are

A backtest pretends to trade in the past. Look-ahead bias is any way the pretend trader sees something it could not have known on that day. Leakage is the machine-learning version: information about the test answers slips into the training data or the inputs. Both make a model look skilled when it is not, and both are silent: nothing crashes.

López de Prado lists cross-validation leakage among the ten reasons most machine-learning funds fail[1]. Every leak below took one line of code.

The setup

MarketA honestB shuffledC centredBuy-and-hold Sharpe
US stocks (SPY)55.3% / 0.3360.8% / 0.8981.8% / 4.930.54
India (NIFTY 50)53.6% / 0.0961.0% / 0.9783.2% / 5.580.55
Forex (EUR/USD)50.4% / -0.1659.0% / 0.6882.8% / 4.67-0.12
Crypto (Bitcoin)51.7% / 0.3859.2% / 1.1981.3% / 6.480.67

Each cell: direction accuracy / Sharpe ratio after costs. All columns cover the same dates.

A. The honest test: walk-forward

Train only on the past, test on the next year, then move forward a year and repeat. We also drop the last 5 days before each test year from training, because their 5-day answers reach into the test year. This drop is called purging.

for year in test_years:
    start = first_day_of(year)
    train = rows_before(start)[:-HORIZON]   # purge labels that reach into the test year
    model.fit(X[train], y[train])
    predictions[year] = model.predict(X[year])

Result: accuracy between 50.4% and 55.3%, and a Sharpe ratio from -0.16 to 0.38. It is the number to believe, because it was scored the way you would trade it.

B. Shuffled k-fold: the neighbours give the answer away

Standard machine-learning code splits rows at random. In a time series that is a leak. Our target covers the next 5 days, so rows a day apart share 4 of the same 5 days. With shuffled folds, a test row's near twin sits in the training set, and the forest remembers it.

# Leaks: rows next to each other in time land on both sides
KFold(5, shuffle=True).split(X)

# Fix: train on the past only, and purge overlapping labels (as in A)

Result: accuracy 60.0% against 52.7%, and a Sharpe ratio of 0.93 against 0.16. Bailey and co-authors show more generally that a hold-out sample used many times stops protecting you[2].

C. A centred moving average: one input that sees the future

A centred average, rolling(21, center=True) in pandas, puts today in the middle of the window, so it uses the next 10 days. It is a normal choice for smoothing a chart. It is a leak in a model.

# Leaks: the window runs 10 days past today
gap = close / close.rolling(21, center=True).mean() - 1

# Fix: a trailing window ends today
gap = close / close.rolling(21).mean() - 1

Result: accuracy 82.3% and a Sharpe ratio of 5.41, against 52.7% and 0.16 honest. One input out of eight did that. The same thing happens with any statistic computed over the whole sample, and with data stamped by the period it covers and not the day it was published.

D. Same-bar trading: the one-line bug

Take a plain rule: long when the price is above its 20-day average, short when below. The signal uses today's close. If the backtest credits that signal with today's return, it trades on a price it only knew at the close.

signal = np.sign(close - close.rolling(20).mean())
ret = np.log(close).diff()

pnl_wrong = signal * ret             # leaks: earns the move that made the signal
pnl_right = signal.shift(1) * ret    # honest: decided at yesterday's close
MarketNext day (honest)Same day (leak)
US stocks (SPY)-0.123.80
India (NIFTY 50)0.174.42
Forex (EUR/USD)-0.363.83
Crypto (Bitcoin)0.714.98

The honest rule's Sharpe ratio averages 0.10. The leak lifts it to 4.26. This is the bug to look for first in any backtest written by someone else, including a chatbot.

E. Scaling on the full sample: real, but small here

Fitting a scaler on all the data before splitting lets the test period's mean and spread into training. It is a real leak, so fit the scaler on training rows only. In our test, with a logistic regression, it barely moved the result. We show it so you can see the size: not every leak is large, and the dangerous ones are those that copy the answer.

MarketScaler on training rowsScaler on all rows
US stocks (SPY)59.5% / 0.7759.5% / 0.80
India (NIFTY 50)55.6% / 0.3055.5% / 0.30
Forex (EUR/USD)47.6% / -0.1847.6% / -0.20
Crypto (Bitcoin)52.3% / 0.1552.4% / 0.18

The price-level illusion

Many tutorials predict tomorrow's price and show a chart where the forecast hugs the real line. That proves nothing. A forecast of "tomorrow equals today" scores almost perfectly on price levels, and below zero on returns, which are what you trade.

MarketR² on pricesR² on returnsDirection right
US stocks (SPY)0.9997-1.2149.3%
India (NIFTY 50)0.9997-0.9353.0%
Forex (EUR/USD)0.9950-1.3648.2%
Crypto (Bitcoin)0.9988-1.0548.0%

R² is the share of the variation that a forecast explains, 1 being perfect. Measure a model on returns, or on direction after costs, and compare it with this do-nothing forecast. Gu, Kelly and Xiu, using machine learning on US stocks, report monthly stock-level R² of only 0.33% to 0.40% for their best models, which still had real economic value[4]. Honest numbers in finance are small.

How to catch look-ahead in code you did not write

Cut the data at a date, recompute, and compare. If the signal on that date changes when later prices are removed, the code uses the future. We ran this test on all 24 strategies a chatbot wrote for us (results).

def uses_the_future(positions, df, checks=18):
    full = positions(df)
    n = len(df)
    for k in range(3, checks + 3):
        cut = n * k // (checks + 3)
        part = positions(df.iloc[:cut + 1])
        a, b = part.iloc[-1], full.iloc[cut]
        if not (np.isnan(a) and np.isnan(b)) and not abs(a - b) <= 1e-9:
            return True    # this date's signal changed when the future was removed
    return False

It catches inputs that see the future, such as a centred window. It does not catch same-bar trading, which lives in the backtest, not the signal, so check the shift there by eye. And it cannot catch a leak in the data itself, such as a figure revised after the fact.

A short checklist

  1. Does each trade earn only returns that come after its signal?
  2. Is every split in time order, with overlapping labels purged?
  3. Does every input use only data published on or before the trade date?
  4. Are scalers and other fitted steps fit on training rows only?
  5. Is the model judged on returns or direction after costs, against a do-nothing forecast?
  6. How many versions did you try? Charge for them with the deflated Sharpe ratio[3].

The free backtest check asks these questions and computes the deflated Sharpe ratio for you.

Go deeper

Sources

  1. López de Prado, M. (2018). The 10 Reasons Most Machine Learning Funds Fail. Journal of Portfolio Management. www.garp.org
  2. Bailey, D. H., Borwein, J., López de Prado, M. and Zhu, Q. J. (2017). The Probability of Backtest Overfitting. Journal of Computational Finance. www.davidhbailey.com
  3. Bailey, D. H. and López de Prado, M. (2014). The Deflated Sharpe Ratio: Correcting for Selection Bias, Backtest Overfitting and Non-Normality. Journal of Portfolio Management. www.davidhbailey.com
  4. Gu, S., Kelly, B. and Xiu, D. (2020). Empirical Asset Pricing via Machine Learning. Review of Financial Studies 33(5). dachxiu.chicagobooth.edu

Fine print: run on 2026-11-08. Seeded and deterministic on the same data. Model: RandomForest(300 trees, min_samples_leaf=5) for A-C; LogisticRegression(C=1) for E; refit yearly, expanding, purged by 5 days. Prices from Yahoo Finance, adjusted. Education only, not advice. Past results, ours included, do not show what a strategy will do next.

Learn quant finance on Thuztra Learn

Thuztra Learn teaches the maths of quant finance from zero, in short lessons, for readers who are unsure of it. Three questions in the app build your quant roadmap, free. Membership is a paid subscription. See what Thuztra Learn is and what it costs, or see the whole Core quant curriculum.

Get the app and answer the three questions:

Download on the Microsoft Store Download for Android

On Android, the app is a direct download, not a Google Play listing. Which devices, and what each one needs.

Also in the library