Tested by BacktestingMax
VWAP cross strategy backtest
Does the VWAP cross strategy make money in day trading?
Mostly no. VWAP is the average price of the day so far, weighted by volume. The VWAP cross strategy buys when price closes above VWAP and sells when price closes below it. We ran 21 backtests from 2020 to 2025, after trading costs: 3 stock indices, 3 forex pairs and gold, each on 5-, 15- and 30-minute charts. Only 3 of the 21 made money: the DAX on 15- and 30-minute charts, and the Nasdaq 100 on 15-minute charts. Of those 3, only the two DAX tests made money in both 2020–2023 and 2024–2025. Every forex, gold and 5-minute test lost money. In 12 of the 21 tests, the trades made money before costs, but the trading costs were bigger.
21 backtests 2020–2025 Trading costs included Tested 12 October 2026
Where it worked 21 backtests- Made money
- 3 of 21 tests, using the basic rules
- Made money in both periods
- 2 2020–2023 and again 2024–2025
- Best backtest
- +215% DAX, 15-minute chart: account growth 2020–2025
- Only the first 2 hours after the open
- 3 tests made money in both periods, compared with 2 using the basic rules
Made money in both periods
- DAX, 15-minute
- DAX, 30-minute
Lost money on every market
- 5-minute charts
The results
Results on every market
Each box shows one backtest of the VWAP cross using 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 | 15-minute | 30-minute |
|---|---|---|---|
| S&P 500 | −91% Lost money. Account down 90% by Feb 2025 | −29% Lost money | −12% Lost money |
| Nasdaq 100 | −41% Lost money | +23% Made money overall, lost in one period | −28% Lost money |
| DAX | −87% Lost money. Account down 90% by Feb 2024 | +215% Made money in both periods | +108% Made money in both periods |
| Gold | −99% Lost money. Account down 90% by Jun 2020 | −98% Lost money. Account down 90% by Jul 2021 | −90% Lost money. Account down 90% by Feb 2024 |
| EUR/USD | −100% Lost money. Account down 90% by Oct 2020 | −100% Lost money. Account down 90% by May 2021 | −99% Lost money. Account down 90% by Dec 2022 |
| GBP/USD | −100% Lost money. Account down 90% by Dec 2020 | −99% Lost money. Account down 90% by Oct 2021 | −99% Lost money. Account down 90% by Jan 2023 |
| USD/JPY | −100% Lost money. Account down 90% by Aug 2020 | −97% Lost money. Account down 90% by Feb 2022 | −82% 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| S&P 500 | 5-minute | 8,194 | 22.6% | 0.95 | −90.7% | −94.2% | −76.2% | −60.7% | −97.4% | $26,679 |
| S&P 500 | 15-minute | 4,327 | 28.6% | 0.98 | −29.0% | −45.8% | −29.6% | +0.8% | −80.3% | $14,079 |
| S&P 500 | 30-minute | 2,686 | 33.5% | 0.99 | −12.2% | −44.5% | +3.9% | −15.5% | −64.7% | $8,963 |
| Nasdaq 100 | 5-minute | 8,410 | 24% | 0.99 | −40.9% | −88.8% | +80.1% | −67.2% | −91.6% | $60,938 |
| Nasdaq 100 | 15-minute | 4,384 | 29.6% | 1.01 | +23.3% | −58.4% | +106.1% | −40.2% | −72.4% | $23,542 |
| Nasdaq 100 | 30-minute | 2,743 | 34.2% | 0.98 | −27.5% | −58.0% | +37.4% | −47.2% | −67.0% | $7,986 |
| DAX | 5-minute | 10,600 | 22% | 0.94 | −86.8% | −97.2% | −84.8% | −12.8% | −97.2% | $13,592 |
| DAX | 15-minute | 5,615 | 27.9% | 1.06 | +215.2% | −55.7% | +65.9% | +90.0% | −70.6% | $23,602 |
| DAX | 30-minute | 3,590 | 32% | 1.07 | +108.4% | −44.8% | +13.2% | +84.2% | −54.4% | $9,639 |
| Gold | 5-minute | 2,582 | 14.4% | 0.64 | −99.4% | −99.4% | −99.4% | 0.0% | −99.6% | $8,489 |
| Gold | 15-minute | 4,498 | 20.7% | 0.79 | −98.4% | −98.6% | −98.3% | −8.9% | −99.0% | $9,087 |
| Gold | 30-minute | 5,036 | 26.4% | 0.89 | −89.7% | −92.4% | −88.2% | −12.7% | −94.4% | $12,588 |
| EUR/USD | 5-minute | 4,067 | 17.8% | 0.78 | −99.7% | −99.7% | −99.6% | −3.7% | −99.8% | $10,027 |
| EUR/USD | 15-minute | 4,424 | 21.3% | 0.79 | −99.5% | −99.6% | −99.4% | −18.1% | −99.7% | $7,740 |
| EUR/USD | 30-minute | 4,738 | 25.9% | 0.79 | −98.8% | −99.0% | −94.7% | −78.2% | −99.2% | $6,147 |
| GBP/USD | 5-minute | 4,209 | 17.7% | 0.84 | −99.6% | −99.7% | −99.6% | 0.0% | −99.7% | $13,244 |
| GBP/USD | 15-minute | 4,919 | 22.4% | 0.86 | −99.4% | −99.6% | −98.8% | −51.5% | −99.6% | $11,133 |
| GBP/USD | 30-minute | 4,866 | 26.2% | 0.81 | −98.7% | −98.8% | −93.6% | −79.8% | −99.1% | $6,773 |
| USD/JPY | 5-minute | 3,316 | 16.5% | 0.71 | −99.7% | −99.7% | −99.7% | 0.0% | −99.8% | $9,913 |
| USD/JPY | 15-minute | 7,612 | 23.8% | 0.84 | −97.2% | −98.1% | −96.3% | −25.4% | −99.0% | $13,259 |
| USD/JPY | 30-minute | 5,072 | 28% | 0.89 | −81.5% | −84.8% | −76.3% | −22.0% | −91.8% | $12,106 |
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 21 backtests show
On forex and gold, the VWAP cross lost money on all 12 tests: EUR/USD, GBP/USD, USD/JPY and gold, each on 5-, 15- and 30-minute charts. In 11 of the 12 tests, the account was down more than 90% from its starting balance at some point. In 6 of the 12, the trades lost money even before trading costs.
On 5-minute charts, the VWAP cross lost money on all 7 markets. A 5-minute chart has more candles, so more candles close on the other side of VWAP. That means more trades, and every trade pays trading costs. On the S&P 500, the 5-minute chart made about 5 or 6 trades a day, compared with about 2 on the 30-minute chart. From 2020 to 2025, the S&P 500's 5-minute trades made $17,613 before costs and paid $26,679 in costs, so the $10,000 account lost 90.7%.
On the stock indices, the trades made money before costs in all 9 tests. But in 6 of the 9, the trading costs were bigger than that profit, including all three S&P 500 tests (see the table below). The DAX was the only market where the VWAP cross made money in both 2020–2023 and 2024–2025: on the 15-minute chart (+215% from 2020 to 2025, the best backtest) and on the 30-minute chart (+108%). Even in the best test, the biggest fall from the highest balance was 56%. No test passed all four of our checks.
The Nasdaq 100 made money from 2020 to 2023 on all three charts, then lost money on all three in 2024–2025. So its results did not last into the newer years. On the 15-minute chart, the $10,000 account grew 106% by the end of 2023, to about $20,600. In 2024–2025 the account fell 40%, to about $12,300. So over the whole test, the account was still up 23%.
Most trades lost money. In all 21 tests, only 14% to 34% of the trades made money (this share is the win rate). Price often crosses VWAP and then crosses back within an hour, and a trade opened on that kind of cross usually ends with a small loss. On the stock indices, most of the profit came from the trades that stayed open for 4 hours or more, on days when price stayed on one side of VWAP.
Trading costs
On the stock indices, trading costs were bigger than the profit in 6 of 9 tests
Each row is one backtest of the VWAP cross using the basic rules, from 2020 to 2025, on a $10,000 account. 'Made before costs' is what the trades made before paying trading costs. Trading costs here are the spread: the gap between the buy price and the sell price.
| Market | Chart | Trades | Made before costs | Trading costs | Result after costs |
|---|---|---|---|---|---|
| S&P 500 | 5-minute | 8,194 | $17,613 | $26,679 | −90.7% |
| S&P 500 | 15-minute | 4,327 | $11,178 | $14,079 | −29.0% |
| S&P 500 | 30-minute | 2,686 | $7,740 | $8,963 | −12.2% |
| Nasdaq 100 | 5-minute | 8,410 | $56,849 | $60,938 | −40.9% |
| Nasdaq 100 | 15-minute | 4,384 | $25,868 | $23,542 | +23.3% |
| Nasdaq 100 | 30-minute | 2,743 | $5,240 | $7,986 | −27.5% |
| DAX | 5-minute | 10,600 | $4,915 | $13,592 | −86.8% |
| DAX | 15-minute | 5,615 | $45,122 | $23,602 | +215.2% |
| DAX | 30-minute | 3,590 | $20,481 | $9,639 | +108.4% |
Every trade risked 1% of the account at that time, so the dollar amounts grow and shrink with the account.
Time of day
On the DAX, the profit came from the first 2 hours
Each bar adds up the results of all the DAX trades opened in that hour, Frankfurt time. The results are in R: 1R is the amount risked on one trade. The bars include all three DAX charts using the basic rules, and all years from 2020 to 2025. Green bars made money in total; red bars lost money.
- 09:00–10:003,518 trades +243.3R
- 10:00–11:003,470 trades +215.3R
- 11:00–12:002,453 trades −11.3R
- 12:00–13:001,919 trades −60.1R
- 13:00–14:001,776 trades −76.4R
- 14:00–15:001,778 trades −25.3R
- 15:00–16:001,873 trades +16.3R
- 16:00–17:002,117 trades −90.1R
- 17:00–17:30901 trades −38.7R
On the S&P 500 and the Nasdaq 100, the pattern was different. Their trades opened in the first 30 minutes after the 09:30 New York open lost 103.8R in total. Their trades opened from 13:00 to 15:00 New York time made 191.1R in total. This fits the result of the first-2-hours change further down: the first-2-hours rule helped on the DAX, but did worse than the basic rules on 4 of the 6 S&P 500 and Nasdaq 100 tests.
The strategy
What the VWAP cross is
VWAP stands for volume-weighted average price. VWAP is the average price since the session opened. A session is the part of the day the strategy trades, for example 09:30 to 16:00 New York time on the S&P 500 (see the first rule). Candles with more volume (more buying and selling) count more in this average. VWAP starts again at every session open, so it draws a new line each day. Many day traders see price above VWAP as a sign that buyers are stronger, and price below VWAP as a sign that sellers are stronger. The VWAP cross strategy buys when a candle closes above VWAP after the candle before it closed below VWAP. It sells (goes short) in the opposite case.
- VWAP
- Each candle's typical price, (high + low + close) ÷ 3, weighted by the candle's volume
- Volume
- The volume in our price data. Forex has no central exchange, and our stock index and gold prices are CFD prices (contracts that follow the market's price). So this is not the volume of the whole market, and our VWAP can differ from the VWAP on an exchange chart.
- Stop-loss
- 1 ATR (the average candle size over the last 14 candles) beyond VWAP, set when the trade opens
- Take-profit
- None. A close on the other side of VWAP, or the end of the session, closes the trade.
- Risk per trade
- 1% of the account
The rules we tested
- Each market has a session, and VWAP starts again at each session open. On the S&P 500 and the Nasdaq 100, the session is the New York stock market's hours: 09:30 to 16:00 New York time. On the DAX, the session is 09:00 to 17:30 Frankfurt time. Forex and gold trade 24 hours a day, so for them the session runs from 08:00 London time (the London open) to 17:00 New York time (the end of the forex day).
- Buy when a candle closes above VWAP and the candle before it closed below VWAP. The trade opens at the next candle's open.
- Sell (go short) when a candle closes below VWAP and the candle before it closed above VWAP. The trade opens at the next candle's open.
- Set the stop-loss when you place the trade: 1 ATR below VWAP for a buy, 1 ATR above VWAP for a sell. ATR is an indicator of how far price moves in an average candle, measured over the last 14 candles of the same chart. The stop-loss does not move after that.
- When a candle closes on the other side of VWAP, close the trade at the next candle's open. If that next candle also closes on the other side of VWAP, open a trade in the new direction at the following candle's open. This also applies when the stop-loss is hit during a candle that closes on the other side of VWAP. After any other stop-loss, wait for the next cross.
- Close any open trade at the end of the session. Don't open a new trade on the session's last candle. On the stock indices, don't trade on that exchange's holidays. On the S&P 500 and the Nasdaq 100, also skip the half days when New York closes at 13:00.
- Size every trade so that, if the stop-loss is hit, the trade loses 1% of the account.
The method
How we tested it
The test
- Markets
- S&P 500, Nasdaq 100, DAX, Gold, EUR/USD, GBP/USD and USD/JPY
- Charts
- 5-minute, 15-minute and 30-minute candles
- Dates
- January 2020 to December 2025
- Account
- $10,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 VWAP cross be improved?
We tried four changes that traders often make to the VWAP cross: trading only in the first 2 hours of the session, a 200 EMA trend filter, taking only crosses made on high volume, and a fixed 2:1 take-profit. The table below explains each one. We ran each change on the same 21 tests. We picked the best change using 2020–2023 only: the change that made money in the most tests, and in a tie, the one with the better middle test. Then we checked it on 2024–2025, the years we did not use to pick it. If you pick a change 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 |
|---|---|---|---|---|
| VWAP cross, basic rules The rules under 'The rules we tested', with nothing added. | 6 of 21middle test −88.2% | 3 of 21middle test −15.5% | 2 | 0 |
| Only the first 2 hours after the openBest on 2020–2023 Open new trades only in the first 2 hours of the session: until 11:30 New York time on the S&P 500 and the Nasdaq 100, until 11:00 Frankfurt time on the DAX, and until 10:00 London time on forex and gold. A trade opened before then can stay open. It closes on the normal rules: a close on the other side of VWAP, the stop-loss, or the end of the session. | 5 of 21middle test −53.8% | 7 of 21middle test −22.9% | 3 | 0 |
| With a 200 EMA trend filter The 200 EMA (exponential moving average) is the average closing price of the last 200 candles on the same chart, with more weight on recent candles. With this filter, only buy when the candle closes above the 200 EMA, and only sell when it closes below it. | 5 of 21middle test −77.0% | 3 of 21middle test −26.3% | 2 | 0 |
| Only crosses on high volume Only take a cross if the candle that crossed VWAP had more volume than the average volume of the last 20 candles, that candle included. Many traders trust a cross more when it comes with high volume. | 4 of 21middle test −73.4% | 4 of 21middle test −22.2% | 2 | 0 |
| Fixed 2:1 take-profit Also close the trade when it has made 2 times what it would lose at the stop-loss (+2R). The normal exits still apply: a close on the other side of VWAP, the stop-loss, or the end of the session. | 4 of 21middle test −93.2% | 0 of 21middle test −36.6% | 0 | 0 |
One change helped, but only on the DAX: trading only in the first 2 hours of the session. In 2020–2023, the years we used to pick, none of the four changes made money in more tests than the basic rules, which made money in 6 of 21. The best of the four was trading only the first 2 hours: 5 of 21, the same as the 200 EMA filter, but with a smaller loss in the middle test (−53.8%, compared with −77.0%). In 2024–2025, the years we kept for the check, the first-2-hours rule made money in 7 of 21 tests, compared with 3 using the basic rules. The first-2-hours rule made money on all three DAX charts in both periods. But from 2020 to 2025, the first-2-hours rule did worse than the basic rules on 4 of the 6 S&P 500 and Nasdaq 100 tests, and lost money on every forex and gold test. The middle test for 2024–2025 lost a little less with the basic rules (−15.5%, compared with −22.9%), mostly because by 2024, 4 forex and gold accounts using the basic rules had almost no money left to trade. The fixed 2:1 take-profit did worst: none of its 21 tests made money in 2024–2025. It closes trades that would have stayed open for hours: with it, the trades in the 9 stock index tests lost 1,120.3R in total, compared with a gain of 131.8R using the basic rules.
Questions
Questions traders ask
Does the VWAP cross strategy work?
Mostly no. In our backtest from 2020 to 2025, after trading costs, the VWAP cross made money in 3 of 21 tests. We tested 7 markets, each on 5-, 15- and 30-minute charts. The 3 tests that made money were the DAX on 15- and 30-minute charts and the Nasdaq 100 on 15-minute charts. Only the two DAX tests made money in both 2020–2023 and 2024–2025. Even the best test, the DAX on 15-minute charts (+215%), had a fall of 56% from its highest balance.
Does the VWAP cross work on forex and gold?
Not in our backtest. On EUR/USD, GBP/USD, USD/JPY and gold, the VWAP cross lost money on all 12 tests. In 11 of the 12, the account was down more than 90% at some point. In 6 of the 12, the trades lost money even before trading costs. We also tried four common changes: trading only the first 2 hours of the session, a 200 EMA trend filter, taking only crosses on high volume, and a fixed 2:1 take-profit. None of them made money on any forex or gold test from 2020 to 2025.
What is the best timeframe for the VWAP cross?
No timeframe made money on most markets. But all 3 tests that made money used 15- or 30-minute charts, and every 5-minute test lost money. A 5-minute chart has more candles, so more of them close on the other side of VWAP. That means more trades and more trading costs. On the Nasdaq 100, from 2020 to 2025, the 5-minute trades made $56,849 before costs and paid $60,938 in costs, on a $10,000 account.
What is the best time of day to trade the VWAP cross?
It depended on the market. On the DAX, the trades opened in the first 2 hours of the session (09:00 to 11:00 Frankfurt time) made money, and the trades opened later lost money overall. With new trades only in those first 2 hours, the VWAP cross made money on all three DAX charts in our backtest, in both 2020–2023 and 2024–2025. But the DAX is one market of seven, so this could be luck. On the S&P 500 and the Nasdaq 100, the trades opened from 13:00 to 15:00 New York time made 191.1R in total (R is the amount risked on one trade). On those two markets, the first-2-hours rule did worse than the basic rules on 4 of the 6 tests.
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 VwapCrossIntraday(Strategy):
"""The intraday VWAP cross, as day traders learn it, plus the changes they try.
VWAP (volume-weighted average price) is the average price of the session so far,
weighted by volume: the sum of (high + low + close) / 3 x volume over the session's
candles, divided by the sum of their volume. It starts again at every session open.
Sessions, each on its own city's clock (daylight saving included):
US indices (S&P 500, Nasdaq 100, Dow, Russell 2000): 09:30-16:00 New York, the stock
market's hours. No trading on days the New York Stock Exchange is closed or closes
early at 13:00.
DAX: 09:00-17:30 Frankfurt. No trading on days the Frankfurt exchange (Xetra) is closed.
Forex and gold trade around the clock, so they use 08:00 London (the London open)
to 17:00 New York (the end of the forex day). So does any market not in the table.
Standard (all switches off): buy when a candle closes above VWAP and the candle before
closed below it; sell short on the opposite cross. A close on the other side of VWAP
closes the trade, and the next candle opens the trade in the new direction if it also
closes on that side. After a stop-loss, wait for the next cross. The stop sits
1 x ATR(14) beyond VWAP; size so that the stop costs 1% of the account. No new trade
on the session's last candle; a trade still open then closes at the session close.
The changes, one at a time:
morning = 1 only open trades in the first 2 hours of the session; open trades
still close on the usual rules
trend = 1 only buy above the 200 EMA, only sell below it
volume = 1 only take a cross made by a candle with more volume than the
average of the last 20 candles
exit_rr = 2 also take profit at 2 times the risk
"""
params = {"atr_mult": Param(type=float, default=1.0, min=0.25, max=5.0),
"morning": Param(type=int, default=0, min=0, max=1),
"trend": Param(type=int, default=0, min=0, max=1),
"volume": Param(type=int, default=0, min=0, max=1),
"exit_rr": Param(type=float, default=0.0, min=0.0, max=10.0)}
lookback = 20
# Session hours: (clock of the open, open, clock of the close, close, exchange calendar),
# with the times in minutes after midnight.
hours = {"USA500IDXUSD": ("America/New_York", 570, "America/New_York", 960, "nyse"),
"USATECHIDXUSD": ("America/New_York", 570, "America/New_York", 960, "nyse"),
"USA30IDXUSD": ("America/New_York", 570, "America/New_York", 960, "nyse"),
"USSC2000IDXUSD": ("America/New_York", 570, "America/New_York", 960, "nyse"),
"DEUIDXEUR": ("Europe/Berlin", 540, "Europe/Berlin", 1050, "xetra")}
around_the_clock = ("Europe/London", 480, "America/New_York", 1020, "")
# Days with no trading, 2020-2025: the exchange is closed, or (New York) closes at 13:00.
closed_days = {
"nyse": "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-11-27 2020-12-24 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-11-26 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-11-25 2022-12-26 "
"2023-01-02 2023-01-16 2023-02-20 2023-04-07 2023-05-29 2023-06-19 2023-07-03 2023-07-04 "
"2023-09-04 2023-11-23 2023-11-24 2023-12-25 "
"2024-01-01 2024-01-15 2024-02-19 2024-03-29 2024-05-27 2024-06-19 2024-07-03 2024-07-04 "
"2024-09-02 2024-11-28 2024-11-29 2024-12-24 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-03 "
"2025-07-04 2025-09-01 2025-11-27 2025-11-28 2025-12-24 2025-12-25",
"xetra": "2020-01-01 2020-04-10 2020-04-13 2020-05-01 2020-12-24 2020-12-25 2020-12-31 "
"2021-01-01 2021-04-02 2021-04-05 2021-12-24 2021-12-31 "
"2022-04-15 2022-04-18 2022-12-26 "
"2023-04-07 2023-04-10 2023-05-01 2023-12-25 2023-12-26 "
"2024-01-01 2024-03-29 2024-04-01 2024-05-01 2024-12-24 2024-12-25 2024-12-26 2024-12-31 "
"2025-01-01 2025-04-18 2025-04-21 2025-05-01 2025-12-24 2025-12-25 2025-12-26 2025-12-31"}
def init(self, ctx):
tz_open, self.open_at, tz_close, self.close_at, calendar = self.hours.get(ctx.symbol, self.around_the_clock)
self.closed = set(self.closed_days.get(calendar, "").split())
self.open_clock = ctx.clock(tz_open)
self.close_clock = self.open_clock if tz_close == tz_open else ctx.clock(tz_close)
self.atr = ctx.atr(period=14)
self.ema = ctx.ema(source=ctx.close, period=200)
self.avg_volume = ctx.rolling_mean(source=ctx.volume, period=20)
self.day = None # the date of the session being traded
self.pv = 0.0 # this session: sum of typical price x volume
self.vol = 0.0 # this session: sum of volume
self.side = 0 # +1 / -1: the last candle closed above / below VWAP (0: none yet)
self.armed = False # True from a cross until a trade is opened in its direction
self.loud = False # the last cross was made on more volume than the 20-candle average
def on_bar(self, ctx):
# VWAP counts the session's candles only, and starts again at each session open.
in_session = (self.open_clock.minute_of_day >= self.open_at
and self.close_clock.minute_of_day < self.close_at
and self.open_clock.date not in self.closed)
if in_session and self.open_clock.date != self.day:
self.day = self.open_clock.date
self.pv, self.vol, self.armed = 0.0, 0.0, False
if in_session:
self.pv += ctx.hlc3[0] * ctx.volume[0]
self.vol += ctx.volume[0]
vwap = self.pv / self.vol if in_session and self.vol > 0 else None
close = ctx.close[0]
side = 0 if vwap is None else (1 if close > vwap else -1)
prev_side, self.side = self.side, side
avg_volume = self.avg_volume.value
if prev_side != 0 and side != 0 and side != prev_side:
self.armed = True # a cross: a new trade may open in its direction
self.loud = avg_volume is not None and ctx.volume[0] > avg_volume
if not in_session:
if ctx.position is not None:
ctx.close_position() # only if the session's last candle was missing from the data
return
if vwap is None:
return
last_candle = self.close_clock.minute_of_day + ctx.timeframe_minutes >= self.close_at
pos = ctx.position
if pos is not None:
if last_candle or (pos.is_long and side < 0) or (pos.is_short and side > 0):
ctx.close_position() # the session close, or a close on the other side of VWAP
return
if last_candle:
return
a = self.atr.value
if a is None:
return
if not ctx.gate("fresh_cross", self.armed):
return
minutes_open = self.open_clock.minute_of_day + ctx.timeframe_minutes - self.open_at # at this candle's close
if not ctx.gate("first_2_hours", ctx.params.morning == 0 or minutes_open <= 120):
return
ema = self.ema.value
trend_ok = ctx.params.trend == 0 or (ema is not None and (close > ema if side > 0 else close < ema))
if not ctx.gate("ema_trend", trend_ok):
return
if not ctx.gate("cross_volume", ctx.params.volume == 0 or self.loud):
return
rr = ctx.params.exit_rr
if side > 0:
stop = vwap - ctx.params.atr_mult * a
tp = close + rr * (close - stop) if rr > 0 else None
ctx.buy(size=ctx.size_by_risk_pct(0.01, stop=stop, entry=close), sl=stop, tp=tp, tag="vwap_long")
else:
stop = vwap + ctx.params.atr_mult * a
tp = close - rr * (stop - close) if rr > 0 else None
ctx.sell(size=ctx.size_by_risk_pct(0.01, stop=stop, entry=close), sl=stop, tp=tp, tag="vwap_short")
self.armed = False
Your turn
Test the VWAP cross yourself
Change the settings, the market or the stop-loss and see the result in seconds. Ask the BacktestingMax AI: Backtest a VWAP cross on the DAX and the S&P 500 15-minute charts, trading only the first 2 hours of the session
.
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 12 October 2026.