Why Renko? And Why Build Your Own EA?
Let's be honest: most retail traders drown in noise. Five-minute charts look like a seismograph during an earthquake, and even H1 can feel choppy. Renko charts solve this by ignoring time entirely and only printing a new brick when price moves a fixed amount. No wicks, no gaps, just clean squares that flip direction. That's why I've spent years automating Renko strategies in MQL5 — the signal clarity is unmatched for trend-following systems.
But here's the problem: MetaTrader 5 doesn't ship with native Renko charting. You can fake it with offline charts or custom symbols, but most "Renko EAs" you find on forums repaint like a bad watercolor. They recalculate past bricks when new data arrives, which makes backtesting a lie. In this post, I'll show you how to build a non-repainting Renko chart EA in MQL5 from scratch. We'll cover brick size selection, signal generation that stays fixed, and how to backtest honestly using MT5's Strategy Tester with custom symbols.
This isn't theory. I've deployed similar EAs on EURUSD and Gold since 2021, and the biggest lesson is this: your brick size is the single most important parameter. Get it wrong, and you're either overtrading micro-moves or missing entire trends. We'll fix that.
Renko Basics: What Every EA Developer Must Understand
A Renko brick has a fixed size — say 10 pips on EURUSD. Price must move 10 pips from the last brick's open or close to print a new one. If price reverses, you need a full brick in the opposite direction to flip the trend. No partial bricks, no time constraints. This makes Renko naturally lagging but incredibly clean.
There are two flavors you'll encounter:
- Classic Renko: Bricks are based on absolute price movement. A new brick appears only when price exceeds the previous brick's high/low by the brick size.
- ATR-based Renko: Brick size adapts to volatility using Average True Range. Popular for ranging markets, but it introduces repainting if you recalculate ATR on every tick.
For our EA, we'll use fixed brick size. Why? Because it's deterministic — no repainting, no look-ahead bias. ATR-based Renko can be useful, but it's a separate topic and requires careful handling to avoid repainting in backtests.
The key insight: Renko signals don't repaint if you generate bricks from tick data or from a fixed timeframe's OHLC. The moment you let the EA recalculate past bricks based on new price action, you've introduced a forward-looking bias. We'll avoid that by building bricks only from historical data and never modifying them.
Building the Renko EA in MQL5: Step by Step
Step 1: Generating Renko Bricks from Tick Data
MQL5 gives us CopyTicks() — a function that fetches raw tick data for any symbol. This is our foundation. We'll read ticks from the standard symbol (e.g., EURUSD) and construct Renko bricks in memory. Each brick is a structure with open, high, low, close, and direction.
Here's a minimal brick structure:
struct RenkoBrick
{
double open;
double high;
double low;
double close;
bool isUp; // true for green, false for red
datetime time; // timestamp of the brick's completion
};We'll store bricks in an array. On each new tick, we check if price has moved enough from the last brick's close to form a new brick. If yes, we append it. If price reverses beyond one brick size, we flip direction and add a brick in the opposite direction.
Critical: never modify existing bricks. Only append new ones. This guarantees non-repainting behavior. Here's the core logic for appending bricks:
void AddBrick(double price, RenkoBrick &bricks[], double brickSize)
{
int total = ArraySize(bricks);
if(total == 0)
{
RenkoBrick first;
first.open = price;
first.high = price;
first.low = price;
first.close = price;
first.isUp = true; // default direction
first.time = TimeCurrent();
ArrayResize(bricks, 1);
bricks[0] = first;
return;
}
RenkoBrick last = bricks[total-1];
double move = price - last.close;
// Check for continuation in same direction
if(last.isUp && move >= brickSize)
{
RenkoBrick newBrick;
newBrick.open = last.close;
newBrick.high = price;
newBrick.low = last.close;
newBrick.close = price;
newBrick.isUp = true;
newBrick.time = TimeCurrent();
ArrayResize(bricks, total+1);
bricks[total] = newBrick;
}
// Check for reversal
else if(last.isUp && move <= -brickSize)
{
RenkoBrick newBrick;
newBrick.open = last.close;
newBrick.high = last.close;
newBrick.low = price;
newBrick.close = price;
newBrick.isUp = false;
newBrick.time = TimeCurrent();
ArrayResize(bricks, total+1);
bricks[total] = newBrick;
}
// Similar logic for last.isDown (not shown for brevity)
}One edge case: if price gaps significantly (e.g., over a weekend), you might get multiple bricks at once. My code handles this by looping until the remaining move is less than one brick size. Without that, you'll miss bricks during high-impact news events.
Another edge case I've seen trip up developers: what happens when the first tick arrives after a long gap? The initial brick open should be set to that tick price, but some EAs incorrectly use the previous day's close. Always initialize from the first tick of your data range, or you'll introduce a bias. I add a flag bool firstTick = true in the OnTick() handler to ensure proper initialization.
Step 2: Brick Size Optimization — The Art of Choosing a Value
Brick size is your EA's only real knob (other than risk settings). Too small, and you'll have dozens of bricks per day — basically a noisy tick chart. Too large, and you'll wait weeks for a signal. I've found a reliable starting point: 2–3 times the average spread for the symbol, or roughly 0.5–1% of the instrument's daily range.
For EURUSD, a 10-pip brick (0.0010) works well on H1 data. For XAUUSD (Gold), I use 50–100 pips (0.50–1.00) because Gold moves roughly 10x the pip value of EURUSD. Here's a quick reference table based on my testing:
| Symbol | Daily Range (pips) | Suggested Brick Size (pips) | Bricks per Day (approx) |
|---|---|---|---|
| EURUSD | 60–100 | 10–15 | 6–10 |
| GBPUSD | 70–120 | 12–18 | 5–8 |
| XAUUSD (Gold) | 800–1500 | 50–100 | 8–15 |
| USDJPY | 50–80 | 8–12 | 5–9 |
These are starting points. You'll want to optimize brick size in the Strategy Tester using a custom symbol (more on that below). Run a genetic optimization over a 6-month period and look for brick sizes that produce the highest Sharpe ratio, not just profit factor. I've seen traders fixate on profit factor above 3, only to blow up because the system had a 40% drawdown. Sharpe ratio above 1.5 is my personal threshold for further testing.
One more thing: brick size interacts with spread and commission. On EURUSD with a 10-pip brick and 1-pip spread, each trade costs about 10% of the brick's range in transaction costs. That's manageable. But on a 5-pip brick, that same spread eats 20% of your potential profit. Always factor in slippage during volatile periods — I add a 0.5-pip buffer in my EA's spread filter.
I also recommend running a quick walk-forward optimization. Take your 6-month optimization period, then test the best brick sizes on the next 3 months out-of-sample. If the Sharpe ratio drops by more than 30%, your brick size is likely overfit. I've had to discard several promising brick sizes this way — it's painful but necessary.
Step 3: Non-Repainting Signal Generation
Our EA will generate signals based on brick patterns. The most reliable pattern on Renko is the double brick breakout: a trend change occurs when two consecutive bricks appear in the same direction after a reversal. For example, after a red brick, you get two green bricks — that's a buy signal. This filters out false flips where price barely breaks the brick size and reverses.
Here's the signal logic in MQL5:
// Check for double brick buy signal
bool CheckBuySignal(RenkoBrick &bricks[])
{
int total = ArraySize(bricks);
if(total < 3) return false;
// Last brick must be up
if(!bricks[total-1].isUp) return false;
// Second-to-last brick must also be up
if(!bricks[total-2].isUp) return false;
// Third-to-last brick must be down (reversal)
if(bricks[total-3].isUp) return false;
return true;
}
// Check for double brick sell signal
bool CheckSellSignal(RenkoBrick &bricks[])
{
int total = ArraySize(bricks);
if(total < 3) return false;
// Last brick must be down
if(bricks[total-1].isUp) return false;
// Second-to-last brick must also be down
if(bricks[total-2].isUp) return false;
// Third-to-last brick must be up (reversal)
if(!bricks[total-3].isUp) return false;
return true;
}Notice: we never look at tick data beyond what's already in the brick array. The bricks are built from historical ticks, and once a brick is closed, it's immutable. This is the core of non-repainting Renko signals.
You can extend this to other patterns. For instance, a triple brick breakout (three consecutive bricks after a reversal) gives fewer signals but higher reliability. I've tested both: double bricks give about 2-3 signals per day on EURUSD with a 10-pip brick, while triple bricks give maybe 1 signal every 2 days. The win rate on triple bricks is around 75% in trending markets, but you'll miss many moves. Pick based on your trading style.
Another pattern worth considering is the brick count divergence: if you see 5 consecutive bricks in one direction, the trend is mature and a reversal signal becomes more reliable. I add a minBricksBeforeSignal input parameter (default 2) to let users control this. Some traders prefer 3 or 4 to avoid early entries in strong trends.
Step 4: Backtesting with Custom Symbols
Here's the dirty secret: you cannot backtest a Renko EA directly on a standard MT5 chart because the platform doesn't support Renko in the Strategy Tester. The workaround is to create a custom symbol that contains pre-built Renko bricks as OHLC bars. This lets you backtest as if you were trading a normal timeframe, but each "bar" is actually one brick.
To set this up:
- Go to Tools > Symbols > Custom Symbols in MT5. You'll find this under the Market Watch context menu or via Tools in the main menu.
- Click "Create Symbol" and name it something like
RENKO_EURUSD_10. The naming convention matters: include the brick size so you can test multiple variants. - Set the symbol properties: tick size = brick size / 10, digits = same as source symbol (e.g., 5 for EURUSD). For a 10-pip brick on EURUSD, brick size in points = 100 points (since 1 pip = 10 points on 5-digit brokers). So tick size = 100 / 10 = 10 points = 0.0010






