Tested by BacktestingMax
Overnight gap fill backtest on NQ and ES futures
Does the overnight gap fill strategy work on NQ and ES futures?
Only a little on NQ, almost not at all on ES, and neither made money in 2024–2025. The overnight gap is the difference between the price at 09:30 New York time, when the US stock market opens, and the previous trading day's close at 16:00. The gap fill strategy trades against gaps of 0.25% to 1%. After a gap up, it sells short (sells first, to profit if price falls back). After a gap down, it buys. It takes profit at the previous close, where the gap fills. The stop-loss is one gap size beyond the 09:30 price, away from the previous close, and a trade still open at 16:00 closes then. We tested three futures from 2020 to 2025: MNQ and MES, the micro contracts of NQ (Nasdaq 100) and ES (S&P 500), and RTY (Russell 2000). Each trade risked 1% of a $50,000 account. In the backtest, after trading costs, the account grew 22.3% on MNQ and 4.6% on MES, and fell 11.8% on RTY. About half of the trades won, and a winning trade made about as much as a losing trade lost. The gap filled more often than the trades won. On MNQ, it filled by 16:00 on 58.0% of the days traded, but only 47.5% of the trades reached the take-profit first.
3 backtests 2020–2025 Trading costs included Tested 26 October 2026
Where it worked 3 backtests- Made money
- 2 of 3 tests, with the basic rules
- Made money in both periods
- 0 2020–2023 and again 2024–2025
- Best backtest
- +22% MNQ (Micro Nasdaq 100), 5-minute chart: account growth 2020–2025
- Take profit at half the gap
- 0 tests made money in both periods, compared with 0 with the basic rules
The results
Results on every market
Each box shows one backtest of the overnight gap fill strategy with the basic rules, from 2020 to 2025, after trading costs: the % result, and a small chart of the account balance. The solid line is the account balance and the dashed line is the starting balance. Green results made money; red results lost money. Under each result: whether it made money in both 2020–2023 and 2024–2025, or passed all four of our checks (see "How a test passes" further down).
| Market | 5-minute |
|---|---|
| MNQ (Micro Nasdaq 100) | +22% Made money overall, lost in one period |
| MES (Micro S&P 500) | +5% Made money overall, lost in one period |
| RTY (Russell 2000 futures) | −12% Lost money |
All the numbers, test by test
| Market | Chart | Trades | Win rate | Profit factor | Result | Max drawdown | 2020–2023 | 2024–2025 | Monte Carlo drawdown | Trading costs |
|---|---|---|---|---|---|---|---|---|---|---|
| MNQ (Micro Nasdaq 100) | 5-minute | 731 | 51.8% | 1.07 | +22.3% | −15.3% | +24.4% | −1.7% | −25.1% | $4,504 |
| MES (Micro S&P 500) | 5-minute | 730 | 50.7% | 1.01 | +4.6% | −14.5% | +9.1% | −4.1% | −29.0% | $8,190 |
| RTY (Russell 2000 futures) | 5-minute | 330 | 49.7% | 0.91 | −11.8% | −25.4% | −7.4% | −4.8% | −25.0% | $3,110 |
Profit factor is the money won divided by the money lost. Above 1 means the test made money. Max drawdown is the biggest fall from the highest balance. 2020–2023 and 2024–2025 show the result in each period on its own. Monte Carlo drawdown: we shuffled the trades into a random order 1,000 times; in 95% of those runs, the worst drawdown was this number or less. Result is the change in the account, with the risk of each trade set to 1% of the account at that time.
The account balance
The best test and the middle test
What we found
What the 3 backtests show
From 2020 to 2025, after trading costs, the basic rules made +22.3% on MNQ and +4.6% on MES, and lost 11.8% on RTY. We also split the test in two: 2020–2023, and the newer years, 2024–2025. A period's result is the change in the account in those years only. With the basic rules, no market made money in both periods. MNQ made +24.4% in 2020–2023 and then lost 1.7% in 2024–2025. MES made +9.1% and then lost 4.1%, and RTY lost money in both. The profit factor (the money won divided by the money lost) was 1.07 on MNQ, 1.01 on MES and 0.91 on RTY, so all three were close to break-even. On MES, trading costs took $8,190 of the $10,505 that the trades made before costs. Costs were high on MES because a typical MES trade needed 5 contracts to risk 1%, compared with 3 on MNQ, and every contract pays the spread and the commission.
The gap filled more often than the trade won. On MNQ, price came back to the previous close by 16:00 on 424 of the 731 days the strategy traded (58.0%). But only 347 trades (47.5%) reached the take-profit before the stop-loss. On 77 days (10.5%), price first moved one more gap size away from the previous close, which hit the stop-loss, and filled the gap only after that. On MES, the gap filled on 51.6% of the days, and 42.9% of the trades reached the take-profit first. So a high gap fill rate does not mean as many winning trades, because on some days price hits the stop-loss before it fills the gap. The table below shows how often, for each gap size.
Many traders say that gaps inside the previous day's range fill more often. The range is the area between the high and the low of the previous day's stock market session, from 09:30 to 16:00. In our test, these gaps did fill more often: on MNQ, 60.8% of the inside gaps filled by 16:00, compared with 54.4% of the gaps outside the range. But as trades, the inside gaps lost money on all 3 markets (see the chart below). On MNQ, inside-gap trades hit the stop-loss first about as often as they reached the take-profit first (46.2% and 45.8%), and they lost money. Outside-gap trades reached the take-profit first much more often than they hit the stop-loss (49.7% and 34.9%). The outside gaps made money on all 3 markets, but we noticed that only after we saw the results, so treat it as an idea to test.
Smaller gaps filled more often, but they were not always the better trades. On MNQ, 67.1% of the gaps of 0.25% to 0.5% filled by 16:00, compared with 46.6% of the gaps of 0.75% to 1%. To compare the trades, we use R: 1R is 1% of the account at the time of the trade, the amount each trade was sized to risk. On MNQ, the 343 trades on small gaps (0.25% to 0.5%) made +5.6R, and the 161 trades on big gaps (0.75% to 1%) made +13.0R. On MES, the small gaps did better: +18.8R in 420 trades, while the big gaps lost 8.7R in 107 trades. So the gap size gave no rule that held on both markets. On a big gap, the take-profit and the stop-loss are both far away. So many trades reached neither and closed at 16:00: 27.3% on MNQ and 41.1% on MES, compared with 2.3% and 9.5% on small gaps.
Many trades ended at the take-profit or the stop-loss soon after 09:30, especially on MNQ. On MNQ, half of the trades that reached the take-profit got there within 28 minutes of 09:30, and 87% got there by 12:00. Half of the stop-losses were hit within 26 minutes. On MES, it took longer: 49 minutes for half of the take-profits, and 63 minutes for half of the stop-losses. The direction of the first 5 minutes made a big difference. On some days, the first 5-minute candle closed toward the previous close: lower than it opened after a gap up, higher after a gap down. On those days, the MNQ trades made +134.5R in total. On the other days, they lost 111.8R. But these trades opened at 09:30, so those results include the first 5 minutes, which you miss if you wait for that candle to close. The version that waits until 09:35 made only +9.2% on MNQ from 2020 to 2025 (+9.4R in total).
Gap fill or winning trade
The gap filled more often than the trade won
Each row is one market and one gap size, or all gap sizes together ('All'), with the basic rules, from 2020 to 2025. 'Gap filled by 16:00' is the share of days when price came back to the previous close at any time between 09:30 and 16:00 New York time. The next three columns show how the trades ended: at the take-profit (the gap filled before the stop-loss was hit), at the stop-loss, or at 16:00. 'Total in R' adds up the results of the trades, after trading costs: 1R is 1% of the account at the time of the trade.
| Market | Gap size | Days traded | Gap filled by 16:00 | Take-profit first | Stop-loss first | Still open at 16:00 | Total in R |
|---|---|---|---|---|---|---|---|
| MNQ | 0.25% to 0.5% | 343 | 67.1% | 50.7% | 46.9% | 2.3% | +5.6R |
| MNQ | 0.5% to 0.75% | 227 | 52.4% | 45.8% | 41.0% | 13.2% | +4.0R |
| MNQ | 0.75% to 1% | 161 | 46.6% | 42.9% | 29.8% | 27.3% | +13.0R |
| MNQ | All | 731 | 58.0% | 47.5% | 41.3% | 11.2% | +22.7R |
| MES | 0.25% to 0.5% | 420 | 61.9% | 49.5% | 41.0% | 9.5% | +18.8R |
| MES | 0.5% to 0.75% | 203 | 40.9% | 36.9% | 37.4% | 25.6% | −3.1R |
| MES | 0.75% to 1% | 107 | 31.8% | 28.0% | 30.8% | 41.1% | −8.7R |
| MES | All | 730 | 51.6% | 42.9% | 38.5% | 18.6% | +7.1R |
| RTY | All | 330 | 71.8% | 49.7% | 50.3% | 0.0% | −11.5R |
The difference between 'Gap filled by 16:00' and 'Take-profit first' is the days when price hit the stop-loss first and filled the gap later. That happened in 77 MNQ trades, 64 MES trades and 73 RTY trades. Futures trade in whole contracts, so most trades risked a little less than 1% of the account: on MNQ, a trade that hit the stop-loss lost 0.88R on average. RTY traded only on days when one contract fit the 1% risk, so 294 of its 330 trades were on gaps of 0.25% to 0.5%. All of its trades ended at the take-profit or the stop-loss before 16:00. A total in R is a sum of trades, so it is not the same as the % change in the account.
Inside or outside the previous day's range
Gaps outside the previous day's range made money; gaps inside it lost
Each bar adds up the results of the trades with the basic rules, from 2020 to 2025, after trading costs. An inside gap opens at 09:30 between the high and the low of the previous day's 09:30 to 16:00 session. An outside gap opens above that high or below that low. The results are in R: 1R is 1% of the account at the time of the trade. Green bars made money in total; red bars lost money.
- MNQ, outside gaps318 trades +34.5R
- MNQ, inside gaps413 trades −11.9R
- MES, outside gaps368 trades +12.2R
- MES, inside gaps362 trades −5.2R
- RTY, outside gaps102 trades +11.4R
- RTY, inside gaps228 trades −22.9R
We tested inside gaps because traders say they fill more often. The inside gaps lost money in both 2020–2023 and 2024–2025, on all 3 markets. The outside gaps made money in both periods on MNQ (+27.6R, then +7.0R) and on RTY (+6.9R, then +4.5R). On MES, they made +14.1R and then lost 1.8R. We noticed that the outside gaps made money only after we saw the results, so treat it as an idea to test, not as a rule. That is why 'only outside gaps' is not one of our versions.
Every version
Each version on each market
Each row is one version of the strategy on one market. The results are the change in the account, after trading costs, risking 1% of the account per trade. 'Basic rules' are the rules under 'The rules we tested' further down. Each other version adds one change to them, explained under 'Can the overnight gap fill strategy be improved?' below.
| Market | Version | Trades | 2020–2025 | 2020–2023 | 2024–2025 |
|---|---|---|---|---|---|
| MNQ | Basic rules | 731 | +22.3% | +24.4% | −1.7% |
| MNQ | Take profit at half the gap | 731 | +0.2% | +8.0% | −7.2% |
| MNQ | Only gaps inside the previous day's range | 413 | −13.0% | −4.1% | −9.3% |
| MNQ | Wait for the first 5 minutes | 338 | +9.2% | +5.3% | +3.7% |
| MNQ | Wait for the first 15 minutes | 257 | +9.3% | +9.5% | −0.2% |
| MES | Basic rules | 730 | +4.6% | +9.1% | −4.1% |
| MES | Take profit at half the gap | 730 | +2.4% | +5.0% | −2.4% |
| MES | Only gaps inside the previous day's range | 362 | −7.3% | −4.6% | −2.9% |
| MES | Wait for the first 5 minutes | 364 | +5.3% | +0.8% | +4.4% |
| MES | Wait for the first 15 minutes | 331 | −9.0% | −3.2% | −6.0% |
| RTY | Basic rules | 330 | −11.8% | −7.4% | −4.8% |
| RTY | Take profit at half the gap | 321 | −8.2% | −8.0% | −0.2% |
| RTY | Only gaps inside the previous day's range | 216 | −16.9% | −10.5% | −7.1% |
| RTY | Wait for the first 5 minutes | 40 | −8.2% | −5.0% | −3.4% |
| RTY | Wait for the first 15 minutes | 11 | −3.3% | −2.9% | −0.4% |
The 2024–2025 result starts from the balance at the end of 2023, so the two periods don't add up to the 2020–2025 result. On MNQ, the biggest fall from the highest balance was 15.3% with the basic rules. It was 7.9% when waiting for the first 5 minutes, and 4.3% when waiting for the first 15 minutes. On RTY, the two waiting versions made too few trades to judge. RTY skipped a day when one contract risked more than 1% of the account, and 1% of a smaller balance is less money. Each version had its own balance, so on RTY the versions skipped different days. That is why taking profit at half the gap made 321 RTY trades, not 330. The inside-gap version made 216, not the 228 inside-gap trades of the basic rules.
The strategy
What the overnight gap fill strategy is
An overnight gap is the difference between two prices. The first is the price at 09:30 New York time, when the US stock market opens. The second is the close at 16:00 New York time on the previous trading day. Stock index futures such as NQ (Nasdaq 100) and ES (S&P 500) trade almost 24 hours a day. So for futures, the gap is how far price moved while the stock market was closed. We measure it as a percentage of the previous close. The gap fills when price comes back to that close. The gap fill strategy expects this to happen on the same day. So it trades against the gap, which traders call fading the gap. After a gap up, it sells short (sells first, to profit if price falls). After a gap down, it buys. Gap and go is the opposite trade on the same gaps: it trades in the direction of the gap. We traded gaps of 0.25% to 1%. Gaps this size happened on about half of the days: on MNQ, on 731 out of the 1,495 full trading days in the test. We tested 3 markets. MNQ (Micro Nasdaq 100) and MES (Micro S&P 500) follow the same prices as NQ and ES, but one contract is one tenth of the size. RTY (the E-mini Russell 2000) is ten times the size of its micro version, which is not in our data. RTY traded on 330 of its 771 gap days. A bigger gap puts the stop-loss further away. On 440 of the other days, one RTY contract would have risked more than 1% of the account, so the backtest skipped them. So most RTY trades were on small gaps. We left out YM (the E-mini Dow): for the same reason, it could trade on only 96 of its 687 gap days.
- Gap
- The price at 09:30 New York time compared with the previous trading day's close at 16:00, as a % of that close. Traded: gaps of 0.25% to 1%, up or down.
- Entry
- At 09:30 New York time, against the gap: sell short after a gap up, buy after a gap down
- Take-profit
- The previous trading day's close, where the gap fills
- Stop-loss
- 1 gap size beyond the 09:30 price, as far from it as the take-profit
- Exit
- At 16:00 New York time, if neither the take-profit nor the stop-loss was hit
- Trades per day
- 1 at most
- Risk per trade
- 1% of the account, rounded down to whole contracts
- Chart
- 5-minute candles. On a 15-minute chart, the basic rules gave the same trades: the entry time, the take-profit, the stop-loss and the 16:00 exit don't depend on the candles. The chart matters only for the versions that wait for the first 5 or 15 minutes.
- Order prices
- The stop-loss closes the trade at the stop-loss price, or at a candle's open if price opens beyond it. The take-profit closes the trade at exactly its price. When price reaches both in the same candle, the backtest looks at the 1-minute candles inside it to see which came first. If both are in the same 1-minute candle, it counts the stop-loss.
- Price data
- BacktestingMax's futures data. It is built from CFD prices (a broker's price that follows the same index). We adjust them to match the real contracts: their price, trading hours, tick (the smallest price step) and roll (the switch from one contract month to the next). It is not exchange data.
- Trading costs
- The spread is the difference between the buy price and the sell price. We used 1 tick: 0.25 points on MNQ and MES, and 0.1 points on RTY. Half of the spread is paid when a trade opens, and half when the trade closes. The take-profit is a limit order, so it pays no spread: the backtest closes the trade at exactly the previous close as soon as price touches it. In live trading, a limit order is not always filled when price only touches it. In this test, price went at least 1 tick past the take-profit in 818 of the 824 trades that reached it. There is also a commission when a trade opens and again when it closes: $0.62 per MNQ or MES contract, and $2.50 per RTY contract.
The rules we tested
- Each trading day, compare the price at 09:30 New York time with the previous trading day's close at 16:00 New York time. The gap is the difference, as a % of that close. On a 5-minute chart, the 16:00 close is the close of the candle from 15:55 to 16:00. The 09:30 price is the close of the candle from 09:25 to 09:30.
- Trade only when the gap is between 0.25% and 1% of the previous close.
- Trade against the gap: sell short after a gap up, and buy after a gap down. The trade opens at 09:30, at the open of the 5-minute candle from 09:30 to 09:35.
- Put the take-profit at the previous day's close. The trade wins if price comes back to that close (the gap fills) before it hits the stop-loss.
- Put the stop-loss one gap size beyond the 09:30 price: above it after a gap up, and below it after a gap down. So the stop-loss and the take-profit are the same distance from the 09:30 price.
- If the trade is still open at 16:00 New York time, close it then.
- Take one trade a day at most.
- Size every trade so that a loss at the stop-loss costs 1% of the account balance at that time. Futures trade in whole contracts, so round down to a whole number of contracts. If even one contract would risk more than 1% of the account, skip the day.
- Don't trade on days the New York Stock Exchange is closed, or closes early at 13:00. After a holiday, the gap counts from the last close before it. After an early close, it counts from the 13:00 close.
The method
How we tested it
The test
- Markets
- MNQ (Micro Nasdaq 100), MES (Micro S&P 500) and RTY (Russell 2000 futures)
- Charts
- 5-minute candles
- Dates
- January 2020 to December 2025
- Account
- $50,000, risking 1% of the account on each trade
- Trading costs
- Included. Every trade pays that market's normal trading cost: the spread, which is the gap between the buy price and the sell price.
How a test passes
We call a test passed only if it meets all four checks. A strategy can make money in one backtest and still fail one of these checks.
- At least 100 trades. With fewer trades, a good result could be luck.
- It made money from 2020 to 2023 and again from 2024 to 2025. This shows whether the strategy kept working in the newer years, not only in the older ones.
- A profit factor (the money won divided by the money lost) of at least 1.2 from 2024 to 2025, after trading costs.
- In 95% of 1,000 random shuffles of the trades, the account must not fall more than 30% from its highest point.
Improvements
Can the overnight gap fill strategy be improved?
Besides the basic rules, we tested four versions, each with one change that traders often make. One takes profit at half the gap. One trades only gaps inside the previous day's range. Two wait for the first 5 or the first 15 minutes to move toward the previous close before entering. Each version ran on the same 3 markets: MNQ, MES and RTY. We picked the best of the four changes using 2020–2023 only: the one that made money on the most markets. In a tie, we picked the one with the better middle result (the median of the 3 markets). Then we checked it on 2024–2025, the years we did not use to pick it. If you pick a version and judge it on the same years, it almost always looks better than it really is.
| Version | Made money 2020–2023years used to pick the best version | Made money 2024–2025years not used to pick it | Made money in both periods | Passed all four checks |
|---|---|---|---|---|
| Basic rules The rules under 'The rules we tested': enter at 09:30 and take profit at the previous day's close. The stop-loss is one gap size beyond the 09:30 price, and a trade still open at 16:00 closes then. | 2 of 3middle test +9.1% | 0 of 3middle test −4.1% | 0 | 0 |
| Take profit at half the gapBest on 2020–2023 The take-profit is halfway between the 09:30 price and the previous close, so the trade wins when price has filled half the gap. The stop-loss stays one gap size away, so a winning trade makes about half of what a losing trade costs. Many gap traders take profit at half the gap, because price gets there more often. | 2 of 3middle test +5.0% | 0 of 3middle test −2.4% | 0 | 0 |
| Only gaps inside the previous day's range Trade only when the 09:30 price is between the high and the low of the previous day's stock market session (09:30 to 16:00 New York time). Many traders say that these gaps fill more often than gaps that open above the previous high or below the previous low. | 0 of 3middle test −4.6% | 0 of 3middle test −7.1% | 0 | 0 |
| Wait for the first 5 minutes Don't enter at 09:30. Wait until 09:35, and enter only if the first 5-minute candle closed toward the previous close: lower than it opened after a gap up, higher after a gap down. The trade opens at the next candle's open, with the same take-profit and stop-loss prices as the basic rules. If the first 5 minutes moved away from the previous close, or already reached the take-profit or the stop-loss, there is no trade that day. | 2 of 3middle test +0.8% | 2 of 3middle test +3.7% | 2 | 0 |
| Wait for the first 15 minutes The same as waiting for the first 5 minutes, but it waits for the first 15 minutes, from 09:30 to 09:45 (the first candle on a 15-minute chart). The trade opens at 09:45. | 1 of 3middle test −2.9% | 0 of 3middle test −0.4% | 0 | 0 |
By our pick rule, no change beat the basic rules on 2020–2023. The basic rules made money on 2 of the 3 markets, and on MNQ (+24.4%) and MES (+9.1%) they made more than any change. Of the four changes, taking profit at half the gap came first by that rule. It made money on 2 markets, +8.0% on MNQ and +5.0% on MES, and had a better middle result than waiting for the first 5 minutes. On 2024–2025, the years we kept for the check, it lost money on all 3 markets: −7.2% on MNQ, −2.4% on MES and −0.2% on RTY. With it, 2 trades in 3 won, but on MNQ a trade that reached the take-profit made about 0.4R, and a trade that hit the stop-loss lost about 0.85R. Waiting for the first 5 minutes was the only change that made money in both periods. It did so on MNQ (+5.3% in 2020–2023 and +3.7% in 2024–2025) and on MES (+0.8% and +4.4%). It traded on about half of the days the basic rules traded: 338 of 731 on MNQ. But it made only a little, and it did not pass all four of our checks. Our pick on 2020–2023 chose a different version. Choosing this one now, because of its 2024–2025 result, would mean picking and judging on the same years. From 2020 to 2025, waiting for the first 15 minutes made +9.3% on MNQ, but it lost 9.0% on MES. On RTY, the two waiting versions made only 40 and 11 trades. Waiting moves the entry away from the stop-loss, so one RTY contract more often risked more than 1% of the account. Trading only gaps inside the previous day's range lost money on all 3 markets, in both periods. So no change made the gap fill clearly better. Waiting for the first 5 minutes held up best in 2024–2025, but it made only a little.
Questions
Questions traders ask
Does the NQ gap fill strategy work?
A little, in our backtest, but not in 2024–2025. We tested MNQ, the micro contract that follows the same price as NQ (Nasdaq 100 futures). Our rules trade against gaps of 0.25% to 1% at 09:30 New York time: they sell after a gap up and buy after a gap down. The take-profit is the previous 16:00 close. The stop-loss is one gap size beyond the 09:30 price, on the other side. From 2020 to 2025, after trading costs, the account grew 22.3%, risking 1% of a $50,000 account per trade. It made +24.4% in 2020–2023 and lost 1.7% in 2024–2025. At one point, the account was 15.3% below its highest balance. Waiting until 09:35 for the first 5 minutes to move toward the previous close made less (+9.2%), but it made money in both periods. Its biggest fall from the highest balance was 7.9%.
Does the ES gap fill strategy work?
Barely, in our backtest. We tested MES, the micro contract that follows the same price as ES (S&P 500 futures). Our rules trade against gaps of 0.25% to 1% at 09:30 New York time and take profit at the previous 16:00 close. The stop-loss is one gap size beyond the 09:30 price. From 2020 to 2025, after trading costs, MES made +4.6%: +9.1% in 2020–2023 and −4.1% in 2024–2025. Trading costs took $8,190 of the $10,505 that the trades made before costs. A typical MES trade needed 5 contracts for a 1% risk, and every contract pays the costs. The small gaps, of 0.25% to 0.5%, did best: +18.8R in 420 trades, where 1R is 1% of the account. The gaps of 0.75% to 1% lost 8.7R in 107 trades. Waiting for the first 5 minutes to move toward the previous close made +5.3%, and it made money in both periods.
How often do overnight gaps fill on NQ and ES?
A gap fills when price comes back to the previous day's 16:00 close. From 2020 to 2025, 58.0% of the MNQ (NQ) gaps of 0.25% to 1% filled by 16:00 New York time, and 51.6% of the MES (ES) gaps. Smaller gaps filled more often: on MNQ, 67.1% of the gaps of 0.25% to 0.5% filled, compared with 46.6% of the gaps of 0.75% to 1%. On MES, it was 61.9% compared with 31.8%. Fills that came before the stop-loss came early: on MNQ, half of the trades that reached the take-profit got there within 28 minutes of 09:30. But a fill is not a winning trade. On some days, price first moved one more gap size away, which hit the stop-loss, and filled the gap later. With the stop-loss of our basic rules, 47.5% of the MNQ trades and 42.9% of the MES trades reached the previous close first.
What is the best chart timeframe for the gap fill strategy?
With the basic rules, the chart makes no difference. The trade opens at 09:30, the take-profit and the stop-loss are fixed prices, and the trade closes at 16:00. In our backtest, the 5-minute and the 15-minute charts gave the same trades. The chart matters only if you wait for the first candle to move toward the previous close before you enter. Waiting for the first 5-minute candle made money on MNQ and MES in both 2020–2023 and 2024–2025, but only a little: +9.2% and +5.3% from 2020 to 2025. Waiting for the first 15-minute candle made +9.3% on MNQ, but it lost 9.0% on MES.
Keep exploring
Related strategies we tested
Gaps Gap and go backtest on NQ and ES futures Made money in 5 of 8 tests, 2 of them in both periods. Best: MES (Micro S&P 500), 5-minute, +115%The strategy code we ran
This is the exact code from the BacktestingMax strategy engine. Paste it into the strategy editor in the app to run it on any market.
class OvernightGapFill(Strategy):
"""Overnight gap fill on US stock index futures: fade the gap at the New York open,
and take profit where the stock market closed the day before.
The gap is the difference between the price at 09:30 New York time, when the US stock
market opens, and the close at 16:00 New York time on the previous trading day (13:00
on a half day), as a % of that close. Index futures trade almost around the clock, so
the gap is how far the futures moved while the stock market was closed. The gap
"fills" when price comes back to the previous 16:00 close.
Standard (all switches off): when the gap is 0.25% to 1% of the previous close, fade
it at 09:30. Sell short after a gap up; buy after a gap down. The gap is measured on
the close of the candle that ends at 09:30, and the trade opens at the open of the
09:30 candle. The take-profit is the previous close, so the trade wins when the gap
fills. The stop-loss is 1 gap size beyond the 09:30 price, so the stop-loss and the
take-profit are the same distance away. A trade still open at 16:00 New York closes
then. One trade a day at most. Size every trade so that the stop-loss costs 1% of the
account, in whole contracts; if one contract would risk more, skip the day. No trading
on days the New York Stock Exchange is closed or closes early (13:00).
The changes, one at a time:
half_target = 1 take profit halfway between the 09:30 price and the previous
close (half the gap); the stop-loss stays 1 gap size away
inside_only = 1 only trade when the 09:30 price is inside the previous day's
09:30-16:00 range (not above its high, not below its low)
confirm_minutes = 5 don't enter at 09:30: wait until 09:35, and enter only if the
first 5 minutes moved toward the fill (closed below their open
after a gap up, above it after a gap down) without reaching the
take-profit or the stop-loss. The trade opens at the next
candle's open, with the same take-profit and stop-loss prices.
confirm_minutes = 15 the same with the first 15 minutes (09:30 to 09:45)
"""
params = {"min_gap_pct": Param(type=float, default=0.25, min=0.0, max=5.0),
"max_gap_pct": Param(type=float, default=1.0, min=0.0, max=10.0),
"stop_gaps": Param(type=float, default=1.0, min=0.1, max=5.0),
"half_target": Param(type=int, default=0, min=0, max=1),
"inside_only": Param(type=int, default=0, min=0, max=1),
"confirm_minutes": Param(type=int, default=0, min=0, max=60)}
lookback = 2
session_open = 570 # 09:30 New York, in minutes after midnight
session_close = 960 # 16:00 New York
early_close = 780 # 13:00 New York, the close on a half day
# Days the New York Stock Exchange is closed, 2020-2025: no stock market open, no trade,
# and the next day's gap counts from the last close before them.
closed_days = (
"2020-01-01 2020-01-20 2020-02-17 2020-04-10 2020-05-25 2020-07-03 2020-09-07 2020-11-26 "
"2020-12-25 "
"2021-01-01 2021-01-18 2021-02-15 2021-04-02 2021-05-31 2021-07-05 2021-09-06 2021-11-25 "
"2021-12-24 "
"2022-01-17 2022-02-21 2022-04-15 2022-05-30 2022-06-20 2022-07-04 2022-09-05 2022-11-24 "
"2022-12-26 "
"2023-01-02 2023-01-16 2023-02-20 2023-04-07 2023-05-29 2023-06-19 2023-07-04 2023-09-04 "
"2023-11-23 2023-12-25 "
"2024-01-01 2024-01-15 2024-02-19 2024-03-29 2024-05-27 2024-06-19 2024-07-04 2024-09-02 "
"2024-11-28 2024-12-25 "
"2025-01-01 2025-01-09 2025-01-20 2025-02-17 2025-04-18 2025-05-26 2025-06-19 2025-07-04 "
"2025-09-01 2025-11-27 2025-12-25")
# Half days: the stock market closes at 13:00. No trade; the 13:00 close is the close
# the next day's gap counts from.
half_days = (
"2020-11-27 2020-12-24 2021-11-26 2022-11-25 2023-07-03 2023-11-24 "
"2024-07-03 2024-11-29 2024-12-24 2025-07-03 2025-11-28 2025-12-24")
def init(self, ctx):
self.ny = ctx.clock("America/New_York")
self.no_trading = set(self.closed_days.split())
self.half = set(self.half_days.split())
self.day = None
self.trading_day = False # True when today is a New York Stock Exchange trading day
self.prev_close = None # the previous trading day's close (16:00, or 13:00 on a half day)
self.prev_high = None # the previous trading day's 09:30-16:00 high
self.prev_low = None # ... and low
self.close_today = None # today's close, once the closing candle is in
self.high_today = None # today's high since 09:30
self.low_today = None # today's low since 09:30
self.first_open = None # the open of the 09:30 candle
self.setup = None # today's gap: (gap up?, take-profit, stop-loss)
self.done = True # True once today's trade is placed, or the day is skipped
def on_bar(self, ctx):
start = self.ny.minute_of_day # this candle's open, New York time
end = start + ctx.timeframe_minutes # this candle's close
if self.ny.date != self.day: # a new day
if self.trading_day: # the day that just ended becomes "the previous day"
self.prev_close = self.close_today # None if its closing candle was missing
self.prev_high, self.prev_low = self.high_today, self.low_today
self.day = self.ny.date
self.trading_day = self.ny.weekday < 5 and self.day not in self.no_trading
self.close_today = self.high_today = self.low_today = self.first_open = None
self.setup = None
self.done = not self.trading_day or self.day in self.half
close_at = self.early_close if self.day in self.half else self.session_close
# Today's stock market session (09:30 to 16:00): its high, its low and its close.
if self.trading_day and self.session_open <= start < close_at:
if start == self.session_open:
self.first_open = ctx.open[0]
self.high_today = ctx.high[0] if self.high_today is None else max(self.high_today, ctx.high[0])
self.low_today = ctx.low[0] if self.low_today is None else min(self.low_today, ctx.low[0])
if end == close_at:
self.close_today = ctx.close[0]
if ctx.position is not None:
if end >= self.session_close or start < self.session_open:
ctx.close_position() # still open at 16:00: closes at the 16:00 candle's open
return
if self.done:
return
wait = ctx.params.confirm_minutes
if self.setup is None:
# The gap, measured at 09:30: the close of the candle that ends at 09:30.
if end != self.session_open:
return
if not ctx.gate("previous_close_known", self.prev_close is not None and self.prev_high is not None):
self.done = True
return
price = ctx.close[0]
gap = price - self.prev_close # above zero: a gap up; below zero: a gap down
gap_pct = abs(gap) / self.prev_close * 100
if not ctx.gate("gap_0_25_to_1_pct", ctx.params.min_gap_pct <= gap_pct <= ctx.params.max_gap_pct):
self.done = True
return
if ctx.params.inside_only == 1 and not ctx.gate(
"opens_inside_previous_day_range", self.prev_low <= price <= self.prev_high):
self.done = True
return
up = gap > 0
stop = price + ctx.params.stop_gaps * gap # beyond the 09:30 price, away from the fill
target = price - gap / 2 if ctx.params.half_target == 1 else self.prev_close
self.setup = (up, target, stop)
if wait == 0:
self._enter(ctx, up, price, target, stop)
return
# Waiting for the first minutes after 09:30 to move toward the fill.
if end < self.session_open + wait:
return
self.done = True # decided at 09:30 + the wait, trade or not
up, target, stop = self.setup
if self.first_open is None or self.high_today is None:
return
toward = ctx.close[0] < self.first_open if up else ctx.close[0] > self.first_open
if not ctx.gate("first_minutes_move_toward_fill", toward):
return
reached = (self.low_today <= target or self.high_today >= stop) if up else \
(self.high_today >= target or self.low_today <= stop)
if not ctx.gate("target_and_stop_not_reached_yet", not reached):
return
self._enter(ctx, up, ctx.close[0], target, stop)
def _enter(self, ctx, up, entry, target, stop):
self.done = True # one trade a day
if abs(stop - entry) <= 0:
return
size = ctx.size_by_risk_pct(0.01, stop=stop, entry=entry)
if not ctx.gate("one_contract_fits_1pct_risk", size > 0):
return # one contract would risk more than 1%
if up:
ctx.sell(size=size, sl=stop, tp=target, tag="gap_up_fade")
else:
ctx.buy(size=size, sl=stop, tp=target, tag="gap_down_fade")
Your turn
Test the overnight gap fill strategy yourself
Change the settings, the market or the stop-loss and see the result in seconds. Ask the BacktestingMax AI: Backtest an overnight gap fill on MNQ, 5-minute chart: at 09:30 New York, fade gaps of 0.25% to 1% from the previous 16:00 close, take profit at that close, stop-loss 1 gap size beyond the 09:30 price, close at 16:00
.
Free account. No credit card needed.
See every strategy we tested Browse strategies shared by tradersBacktest on historical data with trading costs, run by BacktestingMax. Past results do not guarantee future results, and nothing here is financial advice. Updated 26 October 2026.