Win rate 87% was a one-line bug in my verification script — the stop loss was looking at the next six bars.
Hello. I’m lab.aiueo, creating indicators for MT4/MT5 and selling them on GogoJungle.
I’ll continue writing about what happened on the side of the indicator creator. This time is the hardest to write about.
Win rate 87%, PF 2.21
In August 2026, I created a gold-only signal tool. Its name is GD-Signal.
After creating it, I checked its performance against historical data. Gold, 1-hour chart, from January 2011 to August 2026, 15.6 years. 91,586 bars.
The results were as follows.
- Number of trades: 423
- Win rate: 87.0%
- PF (gross profit ÷ gross loss): 2.21
- Longest losing streak: 3
Honestly, I was glad.
From this validation, I put the PF on the product page and in the introduction article as evidence, and started selling.
A few days later, I realized all those numbers were invalid.
There was a bug in the Python script used for validation. It was in one line that determines the range of the stop loss, out of 466 lines.
After fixing it, I ran the same data again and got these results.
| Timeframe (15.6 years) | Before Fix | After Fix |
|---|---|---|
| 1-hour | 423 trades, win rate 87.0%, PF 2.21 | 544 trades, win rate 46.3%, PF 1.07 |
| 4-hour | 201 trades, win rate 81.6%, PF 1.62 | 248 trades, win rate 40.3%, PF 0.68 |
The win rate dropped by half. PF slightly above 1 for the 1-hour chart and below 1 for the 4-hour chart. These numbers do not support the claim of an edge.
I stopped selling and removed the article. Since no one bought it, there was no practical damage.
However, if it had sold, buyers would have purchased based on baseless numbers.
Win rate 87% but wins were too small
Looking back later, the numbers themselves showed a signal.
This is the per-trade profit for the 1-hour chart before the fix.
- Average win: +$4.99
- Average loss: −$15.10
When you win, it’s small; when you lose, it’s three times as big. Even with an 87% win rate, one loss wipes out three wins.
“The win rate is remarkably high, but the win per trade is small.” This combination was the fingerprint of the bug this time.
I will explain the reason in detail later. In short, when determining the stop-loss level, I was looking at six future bars that hadn’t occurred yet.
If you place the stop-loss below the future lows, it won’t be triggered for a while. The win rate increases naturally.
Why I put this story first
Writing about my own failures isn’t easy.
But there are two reasons I chose to start with this story.
First, I wanted to keep a process that doubts the high win rate before celebrating, for my own sake.
Second, if you’re validating trading rules with Python or Excel, you might fall into the same trap.
When you port logic from MT4 language (MQL4) to Python, the price arrangement direction reverses. In this script, I had forgotten to fix that one orientation in one place. Even after porting, I reviewed line by line, and that one spot slipped through again.
This script was created together with AI (Claude Code). I published the numbers, including parts entrusted to AI, myself.
There are no equations here. Only a few lines of code. I’ll explain the sequence of bars so it’s understandable even for those who don’t write Python.
The one buggy line
This is the portion of the gold validation script that calculates the stop-loss.
Before fixing it.
we = min(i + SWING_LEN + 1, n-1)
sl = (l[i:we+1].min() - buf) if is_buy else (h[i:we+1].max() + buf)
After fixing it.
ws = max(i - SWING_LEN - 1, 0)
sl = (l[ws:i+1].min() - buf) if is_buy else (h[ws:i+1].max() + buf)
i is the bar where the signal appeared, and SWING_LEN is 5. l is the sequence of lows, h is the sequence of highs.
For a buy, this means placing the stop-loss slightly below the lowest low within this range of bars.
Only the range is different.
- Before fix: from i to i+6 (the signal bar and the following 6 bars)
- After fix: from i−6 to i (the signal bar and the previous 6 bars)
The previous and the following were swapped completely.
The price sequence direction becomes reversed
Here is why it happened.
The original logic was written as an MQL4 indicator. The range of the stop-loss is expressed as follows.
low[evalBar ~ evalBar+SwingLength+1]
In MQL4, price sequence is0 is the newest bar. As the number increases, it goes back in time.
Therefore “evalBar + 6” looks six bars back from the signal bar.
On the other hand, when reading price data in Python, typically0 is the oldest bar. The higher the number, the future it represents.
Placed side by side, it looks like this.
MQL4 (0 is newest) … [8][7][6][5][4][3][2][1][0] ← right edge is now
Python (0 is oldest) [0][1][2][3][4][5][6][7][8] … ← to the right is the future
Even with the same “+6”, in MQL4 it points to the past, while in Python it points to the future.
Therefore, when porting, you must reverse the direction of all the numbers. In this script, other places were reversed, but the stop-loss line remained in the original form.
What happens when you look six bars into the future
This is the most important part.
The entry is the opening price of the bar after the signal bar. The stop-loss is placed below the lows of the signal bar and the next six bars.
In other words,from entry, for six bars there is no chance of stop-loss triggeringBecause the stop-loss exists below those six bars’ lows (above in the case of a sell; orientation is the same, just reversed).
- 1-hour: 6 hours
- 4-hour: 24 hours
During this period, you cannot lose.
On the other hand, the take-profit target is the nearest previous peak (swing). With six bars, reaching it is not uncommon.
As a result, this happens.
- Wins are small wins that reach nearby targets
- Losses occur only after six bars when the stop-loss placed far away is hit. Therefore, one loss can be large
Win rate 87%, average win $4.99, average loss $15.10. The numbers explain themselves as they are.
“If you place the stop-loss below the future lows, anyone’s win rate will improve.”
It wasn’t that the method was excellent; it was just that I placed the stop-loss by looking at the future.
Why I went through each line of review and yet missed one
This script was built by laying out MQL4 code side by side and checking line by line whether it does the same thing as the original.
This particular line isn’t found by that checking method.
- Original code: from evalBar to SwingLength+1 bars
- Python: from i to SWING_LEN+1 bars
When you compare, it looks exactly the same. This review, which checks for “the same as the original,” judged this line as correct.
The problem was that the wording was the same but the meaning reversed.
What you should have checked was not “the same as the original,” but “is this number before or after the signal bar.”
If you ask the wrong question, you can’t find the same line no matter how many times you look.
In fact, the review did pass.
How I realized it
I found it a few days after producing the numbers.
I had built a similar logic into an EA (automatic trading program) and ran it on MT4 Strategy Tester. It operated with the original MQL4 code on MT4.
The EA’s win rate didn’t match the Python results at all.
To be honest, at first I wrote that it couldn’t be reproduced and set it aside. The testers and Python have many minor differing conditions. I wanted to believe differences were natural.
Nevertheless, I pursued it and recalculated entry, stop-loss, and take-profit one by one. I arrived at the line where stop-loss range pointed to the future.
How I decided the corrected numbers were “correct”
After fixing the bug, the next issue was whether the corrected numbers were actually correct. I might have introduced another mistake while thinking I fixed it.
The deciding factor was the agreement between two independent sources:
- Win rate of the MQL4 EA running on real MT4 hardware
- Win rate of the same currency pair and period run in Python
These two agreed within 1 point.
Two different languages in two different environments converged here. That’s when I finally trusted the corrected numbers.
The gold numbers (win rate 46.3% and PF 1.07) were produced with the same corrected method.
What I do now before presenting numbers
After this incident, I decided to confirm numbers before putting them on product pages or articles. Here are four checks directly related to this bug.
1. Ensure no look-ahead, confirm each number in order
Do this as a separate task from code review.
List every place in the script where price data is read, and ask for each: “Is this number before the signal bar?” If a number greater than i appears, stop at that point.
It’s not “same as the original,” but “is this before the signal bar.” Changing the question makes this line stand out immediately.
2. If win rate exceeds 70%, first suspect a bug
Thinking the method is amazing is last. A win rate above 70% with roughly a 1-to-3 ratio of win size to loss size is a common sign of future-referenced logic like this. When seen, first doubt the stop-loss and take-profit calculations.
3. Do not publish numbers until they match real MT4/MT5 hardware
Don’t rely on Python results alone. Treat them as unverified until they match the tester results on MQL code.
4. Keep the script with the numbers
In fact, this script had been left in a temporary folder and nearly disappeared, leaving only numbers in the document but no way to trace which code produced them.
Now I keep both the pre-fix and post-fix scripts in the same location as the indicator source, and I preserve the tables of numbers alongside explanations.
What happened after that
GD-Signal remains on hold for sale.
There was no bug in the indicator’s core code. The bug was in the code that measured performance.
Afterward, for gold, I created another tool called GD-Deck. In its design phase, I decided not to display win rate or profitability or performance on the product page at all.
If there is no performance-measuring code, there’s nothing in the product that can break from a bug in the measuring code.
Here is the GD-Deck sales page.
Summary
- Win rate 87% and PF 2.21 came from a one-line bug in the validation script. After fixing, win rate is 46.3% and PF 1.07
- The cause was the opposite price sequence directions in MQL4 (0 is newest) and Python (0 is oldest); only the stop-loss range was not flipped
- Placing stop-loss below the six future bars’ lows prevents losses for those six bars. This yields a high win rate with small wins
- A review of “same as original” won’t reveal the issue. Separate task to ask “is this number before the signal bar?”
- Publish numbers only after they match the MT4 real-machine results. Save the scripts along with the numbers
“When evaluating your own tools, the code you use to evaluate is the riskiest part.”
I have believed this since August.
Many who have benefited from validation numbers or been misled by them will understand. I hope this article serves as a caution to doubt before you celebrate.
This article records my own development and validation. The win rates, PF, and other numbers cited are to illustrate validation errors and do not represent performance of any product. They do not guarantee profits from any trading method or product. Please make investment decisions at your own risk.
Thank you for reading to the end.
Next time, I will write about things to check before users shift away from indicators, in the run-up to OANDA Securities' MT4 discontinuation.