Mastering Intraday Backtesting: Beyond the Daily Candle
Why most backtests fail, and how granular intraday data can save your strategy from the graveyard of overfitting.

Let’s look at a candle. Specifically, let's look at the Daily Candle for ETH/USD on May 19, 2021.
On a standard chart, it looks like a "Long-Legged Doji."
- Open: $3,382.66
- High: $3,437.94
- Low: $1,952.46
- Close: $2,460.68
To a textbook technician, this is a signal of "indecision." A neutral pause in the market. But if you zoom in—if you take a microscope to that single red bar—you don't see indecision. You see a war.
You see billions of dollars in liquidations. You see an order book vaporizing in milliseconds. You see arbitrage bots misfiring, stablecoins de-pegging by pennies, and latency arbitrageurs feasting on the chaos.
If you backtested a strategy on the "Daily" timeframe for that day, your bot saw a -27% move. If you backtested on the "1-minute" timeframe, your bot saw a minefield.
This is the central problem of algorithmic trading: The Resolution Trap. The map (Daily Data) is not the territory (Tick Data).
In trading forensics, the most dangerous place is the "wick." A daily candle might show a low of $1,950. But how did it get there?
Did it crash instantly and bounce (a "V-Shape" recovery)? Or did it grind down slowly, creating liquidity pools along the way, before squeezing shorts back up?
- Scenario A (The Flash Crash): Price drops 40% in 15 minutes. Liquidity is non-existent. Slippage is infinite. If your stop loss triggered here, you didn't get out at your price. You got out 10% lower.
- Scenario B (The Slow Bleed): Price drops 40% over 12 hours. Liquidity is thick. Stops are respected.
Two scenarios. Same "Daily Low." One kills your fund. The other makes you rich. Without high-definition data, you cannot distinguish the murder weapon.
Forensic analysis requires evidence. In our case, that evidence is Granular Data.
Most backtests assume a "Perfect Fill." In reality, fills are a function of Volatility and Volume. A sophisticated simulation doesn't just ask "What was the price?" It asks: "What was the cost of the price?" During the May 19th crash, spreads on major exchanges widened from $0.50 to $50.00. That’s 100x the normal cost of doing business. If your backtest didn't capture that, your Sharpe Ratio is a fiction.
The most common "crime" in code is Look-Ahead Bias.
It’s like reading the ending of a mystery novel before solving the case.
If your code says: if (close > open) Buy(), you are cheating. You don't know the "Close" until the day is over.
You must solve the case in real-time. You must calculate your signal at Minute 1, then Minute 2, then Minute 3, never knowing what Minute 4 holds.
To convict a bad strategy, we need to recreate the crime scene. This simulation doesn't just check prices. It creates a "hostile environment" of fees, slippage, and spread widening to see if your alpha survives.
import { Configuration, CandlesApi } from "tickcatcher";
const config = new Configuration({ apiKey: process.env.TICKCATCHER_API_KEY });
const candlesApi = new CandlesApi(config);
async function performForensicBacktest(symbol: string) {
// We define the "Hostility Parameters"
const BASE_SPREAD = 0.0002; // 0.02%
const PANIC_MULTIPLIER = 10; // During crashes, spreads explode 10x
// We load the Tick Data (or 1m granular data)
const evidence = await candlesApi.ultraCandles({
symbol,
timeframe: '1m', // The atomic unit of truth
limit: 10000
});
let balance = 10000;
let position = 0;
for (let i = 20; i < evidence.length; i++) {
const minute = evidence[i];
// Forensic Check: Is this a high-volatility minute?
// We calculate the "Body Size" relative to recent average
const volatility = (minute.high - minute.low) / minute.open;
const isPanicMode = volatility > 0.02; // >2% move in 1 minute
// Adjust Slippage based on the "Crime Scene" conditions
const currentSlippage = isPanicMode ? BASE_SPREAD * PANIC_MULTIPLIER : BASE_SPREAD;
// Strategy Logic (Simple Momentum)
if (position === 0 && minute.close > evidence[i-1].high) {
// ENTRY
const fillPrice = minute.close * (1 + currentSlippage);
position = balance / fillPrice;
balance = 0;
} else if (position > 0 && minute.close < evidence[i-1].low) {
// EXIT
const fillPrice = minute.close * (1 - currentSlippage);
balance = position * fillPrice;
position = 0;
}
}
return balance;
}The market is not a smooth curve. It is a fractal landscape of jagged edges. Most strategies look innocent on a Daily Chart. But when you run the forensics—when you force them to survive the tick-by-tick "war" of the real world—the truth comes out.
Don't assume. Investigate. Backtest with the evidence.