Martingale, Averaging Down, Pyramiding: 172 MT5 Stochastic Backtests on What Position Sizing Changes
This week I set the gold EA aside for a few days and tested an idea that comes up in almost every conversation about simple EAs. Earlier I had repaired a stochastic arrow indicator for a freelance client, and the obvious follow-up question was: instead of all the machinery around the gold breakout, why not a plain EA that enters only on the "reliable" arrows and reverses on the opposite arrow? And while we are at it, what happens if you add averaging down, martingale or pyramiding on top?
Those are fair questions, and the MQL5 Market has many EAs built on exactly those parts. So I wrote the rules down before running anything, ran 172 configurations, made one mistake that produced a beautiful and completely false result, and ended with one small lead that I am now watching on a demo account. This entry is the record.
The rules, fixed before the first run
Each script starts with a header that states the rules, and I did not change them after seeing results. The common parts:
- Data: Dukascopy M1 bid, January 2013 to 24 July 2026, resampled to the timeframe being tested.
- Two stochastic settings: S1 = (5, 3, 3) on high/low, the default most people use, and S2 = (25, 3, 8) on closes, a slower one.
- An arrow is a crossover of the main line and the signal line on a closed bar. The trade is filled at the next bar's open.
- Costs: my broker's measured spreads (gold 0.62 basis points round trip, USDJPY 1.5 pips). Index spreads (S&P 500 0.65, Nasdaq 1.95, DAX 2.05 points) were doubled in the sizing test and multiplied by 1.5 in the placebo test to allow for widening around the daily close. For the index tests, swap was charged for every night held, including on short positions. No slippage was added, which makes the results slightly kinder than reality.
Four ways of filtering the arrows were tested:
- R0: every arrow, always in the market, reversing on each cross.
- R1: only arrows from the extremes (buy crosses with the signal line below 20, sell crosses above 80).
- R2: only arrows in the direction of the trend (buys above a 200-period EMA, sells below it).
- R3: both R1 and R2 together. This is the "reliable arrow" version people usually mean.
The data was split into a first half (2013 to 2019) and a second half (2020 to 2026), so that whatever looked best in the first half could be checked on data it had not been chosen from.
The mistake first
The first version of the short-timeframe test returned something like a 70% win rate with every single year positive. That is not what a stochastic crossover does on gold, and when a result is that good the first job is to find the bug.
It took a few minutes. The position decided at the close of bar t should earn the move from the open of bar t+1 to the open of bar t+2. My array was offset by one, so each position was credited with the move of bar t itself, the very bar whose close produced the arrow. A crossover that happens because price has just moved up was being paid for that move. That is a look-ahead bias of one bar, and it was enough to turn noise into a system that looked ready to sell.
The corrected line is in the script with a comment explaining what the old one did. I mention it because this exact shape of error, one index off in the return series, is easy to make in any backtest written outside MetaTrader, and the version that is easiest to make by accident is the flattering one.
Part 1: arrows on M1 to H1, gold and USDJPY
Two markets, four timeframes, two settings and four filters give 64 cells. After costs, 63 of them are negative. The one positive cell is gold on H1 with the slow setting and the trend filter (R2): +0.27 basis points per trade over 6,116 trades, with 8 of 14 years positive. That is a rounding error, not an edge, and it is the best of 64.
Before costs the picture is not much better. On M1 to M15 the average per trade sits within a fraction of a basis point of zero in both directions. The arrows carry almost no information about the next move, and every reversal pays the spread. For reference, the cell that matches the configuration I later put on demo (gold M15, default setting, every arrow) made 84,537 trades, about 520 a month, at −0.085 basis points before costs and −0.705 after.
Two details are worth pointing out, because they cut against what most people would expect.
First, filtering for "reliable" arrows did not help. On gold M15 with the default setting, taking every arrow gave −0.705 basis points per trade after costs. Taking only arrows from the extremes gave −0.878, and the full "reliable" filter gave −0.838. Fewer trades, each one a little worse. The filter reduced the bill without improving what was being bought.
Second, the first-half winners did not survive. The best cell of 2013 to 2019 was gold H1, slow setting, reliable filter: +0.19 basis points per trade over 639 trades. In 2020 to 2026 the same cell made −2.68 over 579 trades. The second-best first-half cell went from +0.18 to −0.72. If I had picked from the first half and launched, I would have launched one of the three worst cells of the second half.
Part 2: averaging down, martingale and pyramiding
The second script moved to daily bars, added three stock indices (S&P 500, Nasdaq 100, DAX) alongside gold and USDJPY, and applied four sizing schemes on top of the same entries:
- base: one unit per trade.
- averaging down: add one unit each time price moves one ATR(20) against the position, up to four units.
- pyramiding: add one unit each time price moves one ATR in favour, up to four units.
- martingale: after a losing trade, double the next trade's size (capped at 16x); after a win, go back to one.
A 20-day time stop applies to everything, because I do not run any position without a time limit. The table below compares the medians across the ten market-and-setting combinations that use every arrow (R0). Drawdown and worst trade are in basis points of one unit's notional, so the figures are comparable across markets.
| Median of 10 combinations | base | martingale |
|---|---|---|
| Share of start dates in profit 90 days later | 46% | 59% |
| Maximum drawdown | 8,880 bp | about 36,700 bp (4.1x) |
| Worst single trade | 1,089 bp | 5,614 bp (5.2x) |
| Yearly result divided by maximum drawdown | −0.045 | −0.03 |
This is the result I most wanted to write down. Martingale made the most visible statistic better. If you start on a random day, you are more likely to be up after three months: 59% of start dates instead of 46%. A buyer looking at a short track record, or at a signal that has been running for a quarter, would see a smoother and more often green account. At the same time the maximum drawdown was about four times larger and the worst single trade about five times larger, and the ratio of return to drawdown barely moved, from −0.045 to −0.03, negative before and after.
One cell shows it plainly. On the S&P 500 with the default setting and every arrow, the base version lost about 1,193 basis points a year and was in profit after 90 days from 34% of start dates. The martingale version lost about 4,771 basis points a year, four times as much, and was in profit after 90 days from 54% of start dates. It lost more money and looked better more often. That is what doubling after losses does: it converts many small losing streaks into small wins, and stores the cost in a few very large losses that arrive rarely.
The other two schemes were less dramatic but pointed the same way:
- Averaging down left the average per trade roughly where it was and made the worst trade worse: the median worst trade moved from 1,089 to 1,234 basis points with every arrow, and from 402 to 654 with the reliable buy-only entries.
- Pyramiding lowered the win rate (median 37% down to 30%) and increased drawdown. Adding to winners only pays if the winners keep running, and on these entries they mostly did not.
In 53 of the 60 comparisons the sign of the total did not change. The seven that flipped were all entries that sat close to zero to begin with (the largest base result among them was 464 basis points a year), and the two that martingale turned positive, gold and USDJPY with every arrow, did so while roughly doubling and sevenfolding the drawdown (9,045 to 19,056 and 2,691 to 19,403 basis points). Sizing decides how a result is distributed over time; on an entry with no edge it can make the total land on either side of zero, but it does not create the edge.
Part 3: the one lead, and how I checked it
One thing in the daily table did look different. On stock indices, the reliable buy-only version (an uptrend above the 200-day EMA, plus an arrow out of the oversold zone, exit on the next opposite arrow or after 20 days) was positive in every index. Positive is not the same as informative, though. Stock indices went up for most of this period, so almost any buying rule held for a few days will look positive. The question is whether the arrow adds anything over simply buying on an uptrend day.
So the third script compared each version against 2,000 random sets of entries with the same number of trades and the same holding periods, drawn only from days when the index was above its 200-day EMA. The arrow's contribution is the difference between its average and the random average.
| Daily bars | Trades | Arrow average | Random uptrend average | Difference | Random beats arrow |
|---|---|---|---|---|---|
| S&P 500, default setting | 48 | +68.7 bp | +15.1 bp | +53.6 bp | 0.9% of draws |
| S&P 500, slow setting | 34 | +103.7 bp | +29.3 bp | +74.3 bp | 2.3% of draws |
| Nasdaq 100, default | 57 | +32.7 bp | +26.1 bp | +6.6 bp | 40.8% |
| DAX, default | 52 | +33.0 bp | +7.4 bp | +25.7 bp | 17.9% |
On the S&P 500 the arrow did better than random uptrend entries in all but about 1 to 2 out of 100 draws. On the Nasdaq and the DAX the difference is within what random entries produce. On H4 and H1 bars the number of trades rises to one to five a month, but the difference mostly disappears. The exceptions are the Nasdaq on H4 with the slow setting (+16.6 basis points, random entries beat it in 3.0% of draws) and the H1 slow-setting cells on the S&P 500 and Nasdaq (+3.6 and +5.8 basis points), and the last two are inside the uncertainty of the cost model.
I do not want to make this look stronger than it is, so here are the reasons to be careful:
- It is 48 trades in thirteen and a half years, about three or four a year.
- Most of the average comes from the second half. In 2013 to 2019 the default version averaged +17.1 basis points per trade, in 2020 to 2026 +135.1.
- The placebo test had 18 cells (three indices, three timeframes, two settings). Three of them came in under 2.5%. Chance alone would give about half a cell at that level, so three is more than chance, but the cells are not independent of each other, and the S&P 500 pair is essentially one finding counted twice.
- Short-term mean reversion in US stock indices is a well-documented effect. This may just be a stochastic-flavoured way of measuring something already known, which is a reason to think it is real but not a reason to think it is mine.
- I found afterwards that my daily bars were cut at midnight UTC rather than at the broker's 21:00 UTC close, because a resampling offset argument did not take effect; the warning was in the log and I only read it afterwards. I do not expect it to change the conclusion, but it is a known flaw in these numbers.
What is running now
I wrote one small experimental EA with all of these switches as inputs, and since 4 November it has been running on a demo account in five copies:
- S&P 500 daily, reliable buy-only arrows, no adding. This is the one lead. At three or four trades a year, judging it will take years, not weeks.
- Gold M15, every arrow, reversing, in four versions: no adding, averaging down, pyramiding and martingale. This one is not a lead. It is there to check that the backtest describes the live behaviour.
Before starting the demo run I checked that the EA's arrows match the Python formula. The last five arrows it printed on gold M15 agreed with the script on time, %K, %D and closing price. I have not run the strategy tester on it yet, because the machine I use for testing was busy with another job, so the first days of demo logs are doing that work instead.
The predictions for the four-week readout on 2 December are written down in the lab notes and will not be edited:
- No adding: average per trade negative, around −0.7 basis points.
- Martingale: a higher share of winning days than no adding, and a worst day at least three times as bad.
- Averaging down: about the same average as no adding, with a worse single worst trade.
- Pyramiding: a lower win rate than no adding.
- If all four finish the month positive, I will not read that as a good EA. One month is well inside the noise.
The martingale and averaging-down versions exist only to be watched on demo. They will not go on a live account and they will not be sold.
What I take from it
Three things.
The "reliable arrow" filter is mostly a way of trading less. On short timeframes it cut the number of trades and did not improve them on average. If an indicator has no information about direction, choosing the moments when it looks most confident does not add any.
Position sizing is not a source of edge. It is a choice of how to experience the edge you already have, or the lack of it. Martingale in particular moves losses out of the period most people look at, the first few months, and concentrates them in the tail. That makes it effective at producing good-looking short track records, which is exactly why a buyer should be suspicious of any short track record whose position sizes grow after losses.
And the most convincing result in this whole exercise was a bug. The fake version had a 70% win rate and no losing years. The real version had 63 losing cells out of 64. When a backtest of a simple idea looks like that, I now assume the error is in my code until I have found where it is.
For the trial count: this exercise added 172 configurations (64 short-timeframe cells, 90 daily sizing runs, 18 placebo cells). One of them is on demo as a lead (the other four demo copies exist only to check the backtest against live behaviour), and it is there because it beat a random baseline, not because it had the best numbers in the table.


