DEV Community

Benjamin-Cup
Benjamin-Cup

Posted on

Your Trading Backtest Might Be Cheating: Understanding Look-Ahead Bias

Have you ever built a trading strategy that achieved an 80–90% win rate in backtesting, only to lose money after going live?

Before blaming latency, slippage, or the market, check one thing:

Is your backtest using information from the future?

This is called look-ahead bias.

I've encountered this while developing Polymarket trading bots. A strategy can look extremely profitable in historical testing simply because the backtest has access to information that a live bot wouldn't have.

Let's look at some common examples.


What Is Look-Ahead Bias?

The basic rule is simple:

A trading decision can only use information that was available at that exact moment.

Imagine your bot enters at the beginning of a candle.

This is a problem:

signal = df["close"][i] > df["open"][i]
enter_at = df["open"][i]
Enter fullscreen mode Exit fullscreen mode

The candle hasn't finished yet, so the closing price isn't available.

Instead:

signal = df["close"][i - 1] > df["open"][i - 1]
enter_at = df["open"][i]
Enter fullscreen mode Exit fullscreen mode

Now the signal is based on a completed candle.

That small difference can have a huge impact on backtest results.


1. Future Candle Data

This is one of the most common mistakes.

If your strategy makes a decision at time t, it shouldn't know:

  • future close
  • future high/low
  • future volume
  • future returns
  • future indicators

A historical dataset contains all of this information.

A live trading bot doesn't.

Your backtest needs to enforce the same timeline.


2. Future Daily Statistics

Consider a strategy that trades at 9:00 AM.

If its features include:

Today's High
Today's Low
Today's Volume
Today's Close
Enter fullscreen mode Exit fullscreen mode

there's a problem.

At 9:00 AM, most of these values aren't final.

Because your historical dataset already contains the complete day, your code might accidentally expose the future to the strategy.

Every feature should have a clear question:

When did this information become available?

If the answer is after the trade decision, it shouldn't be used.


3. Data Preprocessing Can Leak Information

Look-ahead bias can also happen before your strategy even runs.

For example:

# WRONG
scaler.fit(all_data)
X = scaler.transform(all_data)
Enter fullscreen mode Exit fullscreen mode

The scaler has now seen the entire dataset, including future observations.

A more realistic approach is:

# CORRECT
scaler.fit(data[:t])
X_now = scaler.transform(data[t])
Enter fullscreen mode Exit fullscreen mode

For walk-forward testing, preprocessing needs to follow the same timeline as the strategy.


4. Centered Rolling Indicators

Be careful with rolling calculations too.

For example:

# WRONG
df["ma"] = df["close"].rolling(
    20,
    center=True
).mean()
Enter fullscreen mode Exit fullscreen mode

A centered window can use prices from both before and after the current timestamp.

Use a trailing window instead:

# CORRECT
df["ma"] = df["close"].rolling(20).mean()
Enter fullscreen mode Exit fullscreen mode

The indicator should only depend on information that has already happened.


5. Leaked Labels

This is especially dangerous for machine-learning strategies.

Suppose you're trying to predict the outcome of a prediction market.

If your input features accidentally contain:

  • settlement results
  • resolution prices
  • future returns
  • post-resolution data
  • future market statistics

then the model isn't really predicting anything.

It's seeing the answer.

You can easily end up with a model showing 99% accuracy in testing that performs terribly in production.


Why Does Look-Ahead Bias Produce Great Results?

Because future information is extremely valuable.

If your strategy knows what happens next, it becomes much easier to make profitable decisions.

That's why look-ahead bias can create:

  • extremely high win rates
  • smooth equity curves
  • unrealistic Sharpe ratios
  • tiny drawdowns
  • suspiciously consistent returns

The dangerous part is that nothing crashes.

The code works.

The backtest completes.

The results look great.

The problem is the timeline.


A Simple Way to Structure a Backtest

One approach I use is to make the historical boundary explicit:

for t in range(start, end):

    history = data[:t]

    decision = strategy(history)

    outcome = data[t]

    record(decision, outcome)
Enter fullscreen mode Exit fullscreen mode

The strategy receives:

Past → Decision
Enter fullscreen mode Exit fullscreen mode

The backtest then evaluates:

Decision → Future Outcome
Enter fullscreen mode Exit fullscreen mode

The strategy should never get access to the second part when making the decision.


Historical Data Matters

This became particularly important for my Polymarket research.

I've been recording historical market data directly from on-chain sources and the Polymarket API instead of relying only on reconstructed datasets.

I archive 5-minute cryptocurrency market data locally and use it for strategy development and backtesting.

Having your own historical dataset gives you much more control over:

  • timestamps
  • market state
  • price history
  • market lifecycle
  • missing data
  • available information
  • backtesting assumptions

I've used this infrastructure while researching and developing several Polymarket trading strategies, including end-cycle and BTC/ETH hedge strategies.

The goal isn't to produce an impressive backtest.

The goal is to produce a backtest that behaves like the real trading environment.


Backtesting Still Isn't Live Trading

Even after removing look-ahead bias, a backtest is still a simulation.

You may need to account for:

  • latency
  • slippage
  • liquidity
  • order-book depth
  • partial fills
  • rejected orders
  • API delays
  • trading fees
  • position limits
  • changing market conditions

A realistic backtest should answer:

Could my bot actually have made this decision with the information available at that moment?

That's much more useful than simply asking:

Was the strategy profitable on historical data?


Final Takeaway

When a backtest looks too good to be true, check the timeline before changing the strategy.

For every feature, indicator, and dataset, ask:

When did this information become available?

If it became available after the trading decision, it's future information.

And if your backtest can see the future, your strategy isn't being tested.

It's being given the answer.


More Polymarket Bot Development

I'm building and researching Polymarket trading infrastructure, historical data collection, automated execution, and backtesting.

You can find some of my work here:

GitHub:

https://github.com/Benjam1nCup/Polymarket-trading-bot-python-V2

Telegram:

https://t.me/BenjaminCup

If you're working on trading bots, prediction markets, or quantitative research, I'd be interested in hearing about your approach.

Top comments (0)