Русский
preview
Unified Multi-Timeframe Renko: Synthesizing the Market's Temporal Dimensions

Unified Multi-Timeframe Renko: Synthesizing the Market's Temporal Dimensions

MetaTrader 5Trading |
510 5
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introduction: When a Single Glance Is Not Enough

Imagine the market through four windows: M5 — the nervous twitches of speculators; M15 — the breathing of intraday traders; H1 — the intentions of position traders; H4 — global trends. These realities exist in parallel, but they do not interact. The trader switches between charts, searching for alignment, like an orchestra without a conductor.

Standard Renko does not answer the question of what is happening on higher timeframes. The market is multi-layered: M5 whispers of momentum, H1 — of medium-term rhythm, H4 — of a major wave. The idea: a unified Renko that breathes at all frequencies. It is a bridge between noise and structure, filtering out fluctuations and revealing the direction of capital.

Markets are fractal: chaos on minute charts gains meaning on hourly charts; trends are made up of micro-trades. Classical analysis is sequential: the global trend on D1, entry on H1, timing on M5. It requires subjective judgment.

Multi-timeframe Renko is parallel synthesis: signals are weighted into a single decision, like a neural network. The EMA acts as a direction indicator, with greater weight assigned to recent data. The four opinions converge into a consensus, creating a synthetic symbol in MetaTrader 5. It receives ticks in real time, draws bars, and appears in the symbol list. An approach for testing strategies, indicators, and automated trading based on a filtered market reflection.



System Architecture: From Concept to Code

Components: tick capture, four EMA and direction analyzers, and a signal synthesizer.

struct TimeframeData {
    ENUM_TIMEFRAMES timeframe;
    double emaValue;
    double lastRenkoPrice;
    double weight;
    int renkoDirection; // 1 = up, -1 = down, 0 = sideways market
    datetime lastUpdateTime;
    double signalStrength;
};

This structure is the heart of the system. It stores the complete state of each timeframe: the current EMA value, the latest Renko brick price, the timeframe’s weight in the overall decision, the direction of the latest movement, the time of the last update, and the strength of the current signal. The architecture's modularity makes it easy to add new timeframes or adjust the weights of existing ones without rewriting the logic.

Timeframe weights are a critical parameter of the system. By default, the progression {1.0, 2.0, 3.0, 4.0} is used for M5, M15, H1, and H4, respectively. This means that H4 has four times as much influence on the final decision as M5. The logic is simple: the higher the timeframe, the less noise it contains and the more reliable its signals are. However, these weights can and should be adjusted to suit specific trading strategies and the characteristics of the assets.

The system operates in two modes: historical and real-time. In historical mode, it loads data for the specified period, processes it sequentially, and creates an array of Renko bars. In real-time mode, it connects to the tick stream via a timer and updates the virtual symbol as new data arrives. This allows the system to be used for both backtesting and live trading.



Creating a Virtual Symbol: The Magic of Custom Symbols

MetaTrader 5 offers a unique ability to create custom symbols — one of the platform’s most powerful yet underappreciated features. A custom symbol is not just a data set; it is a full-fledged trading instrument with all the attributes of a real symbol: quote precision, contract size, profit currency, and trading sessions.

bool CreateUnifiedSymbol()
{
   if(SymbolSelect(UnifiedSymbol, true))
   {
      CustomRatesDelete(UnifiedSymbol, 0, 0);
      Print("Symbol ", UnifiedSymbol, " exists. History deleted.");
      return true;
   }

   if(!CustomSymbolCreate(UnifiedSymbol))
   {
      Print("Symbol creation error: ", GetLastError());
      return false;
   }

   double point = SymbolInfoDouble(SourceSymbol, SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(SourceSymbol, SYMBOL_DIGITS);

   CustomSymbolSetInteger(UnifiedSymbol, SYMBOL_DIGITS, digits);
   CustomSymbolSetDouble(UnifiedSymbol, SYMBOL_POINT, point);
   CustomSymbolSetString(UnifiedSymbol, SYMBOL_DESCRIPTION, 
      "Unified Multi-TF Renko for " + SourceSymbol);
   
   return true;
}

This code performs several critical operations. First, the system checks whether a symbol with that name already exists — if so, its history is cleared to avoid conflicts. A new symbol is then created using CustomSymbolCreate(). Next, the main properties are set: the number of decimal places, the point size, and the description.

Important note: A virtual symbol inherits most of its properties from the original symbol. This ensures that profit and loss calculations will be accurate and that the symbol itself will look natural in the trading terminal. You can even configure trading sessions, although this is usually not critical for Renko charts, since they are based on price changes rather than time.

Now we have a “live” symbol in the terminal that we can open on a chart, run in the Strategy Tester, attach indicators to, and even build Expert Advisors for. All that is left is to fill it with bars. And this is where the real magic begins.



Brick Size: Adapting to Volatility

A Renko brick can be either fixed or adaptive. A fixed size is simple and straightforward: each brick always has the same height in points. This works well in stable markets, but creates problems with volatile assets. When the ATR (Average True Range) rises sharply, fixed bricks start being drawn too often, cluttering the chart with noise. When volatility drops, bricks form too infrequently, and the chart becomes less informative.

Adaptive sizing solves this problem elegantly: the system calculates the daily ATR and multiplies it by a factor — usually between 0.3 and 0.7. This produces a brick size that automatically scales to current market activity.

double CalculateBrickSizeFromATR()
{
   double atr[];
   int handle = iATR(SourceSymbol, PERIOD_D1, ATRPeriod);
   if(handle == INVALID_HANDLE) return -1;

   int copied = CopyBuffer(handle, 0, 0, 1, atr);
   IndicatorRelease(handle);

   if(copied != 1) return -1;
   return NormalizeDouble(atr[0] * ATRMultiplier, _Digits);
}

The function is extremely simple, yet effective. It creates an ATR indicator handle on the daily timeframe, copies the latest value, frees up resources, and returns the normalized brick size. Using the daily period is critical — it provides a stable measure of volatility that is not subject to intraday spikes.

The ATR multiplier is an art, not a science. A value of 0.5 yields a balanced result for most currency pairs. Lower values (0.3–0.4) make the chart more sensitive, with more bricks and more detail. Higher values (0.6–0.8) create a smoother picture by filtering out minor fluctuations. The choice depends on your trading style: scalpers need small bricks, while swing traders need large ones.

This approach makes the chart “self-regulating”: as volatility increases, the bricks become larger; as the market quiets down, they become smaller. This eliminates the need to constantly adjust the settings manually as the market moves from quiet periods to news releases or economic shocks.



Composite Signal: Three Combination Methods

The most interesting and nontrivial aspect of the system is the mechanism for combining signals from different timeframes. Three approaches are implemented here, each with its own advantages and areas of application.

Method 1: Simple average

The most naive, yet surprisingly effective approach. We take the EMA values from all four timeframes and calculate the arithmetic mean.

double CalculateAverageSignal()
{
   double sum = 0;
   for(int i = 0; i < 4; i++)
   {
      sum += timeframes[i].emaValue;
   }
   return sum / 4.0;
}

This method gives all timeframes equal weight. M5 and H4 contribute equally to the final signal. It might seem that this is wrong — after all, higher timeframes are more reliable. But in practice, the simple average produces a surprisingly well-balanced chart that captures both short-term momentum bursts and long-term trends.

Application: suitable for scalping and intraday trading, where responsiveness to rapid price movements is important. It works well on highly liquid currency pairs such as EURUSD, where market noise is relatively low.

Method 2: Weighted average

A more sophisticated approach is to assign a weight to each timeframe that reflects its importance.

double CalculateWeightedSignal()
{
   double weightedSum = 0;
   double totalWeight = 0;
   
   for(int i = 0; i < 4; i++)
   {
      weightedSum += timeframes[i].emaValue * timeframes[i].weight;
      totalWeight += timeframes[i].weight;
   }
   
   return totalWeight > 0 ? weightedSum / totalWeight : 0;
}
With weights {1.0, 2.0, 3.0, 4.0}, H4 accounts for 40% of the total weight, giving it the largest individual influence on the final signal. This creates a chart that follows the higher timeframes without completely ignoring the lower ones.

Use case: ideal for position trading and swing strategies. The chart is smoother, with fewer false signals, but it is also less sensitive to short-term opportunities. It performs very well on volatile crosses such as GBPJPY.

Method 3: Consensus

The most conservative and reliable approach. A brick is drawn only when at least three of the four timeframes agree on the direction.

double CalculateConsensusSignal()
{
   int upCount = 0, downCount = 0;
   double avgUp = 0, avgDown = 0;
   
   for(int i = 0; i < 4; i++)
   {
      if(timeframes[i].renkoDirection > 0)
      {
         upCount++;
         avgUp += timeframes[i].emaValue * timeframes[i].weight;
      }
      else if(timeframes[i].renkoDirection < 0)
      {
         downCount++;
         avgDown += timeframes[i].emaValue * timeframes[i].weight;
      }
   }
   
   if(upCount >= 3) return avgUp / upCount;
   if(downCount >= 3) return avgDown / downCount;
   
   return CalculateWeightedSignal();
}

This method simulates a “democratic vote” among timeframes. If three or four EMAs are rising, an upward brick is created. If three indicate a decline, a downward brick is created. If there is no consensus (for example, 2 up, 2 down), the system reverts to the weighted average as a compromise solution.

Use case: works exceptionally well on currency pairs with frequent micro-fluctuations — EURUSD and USDCAD. There are significantly fewer bricks, but each one has a high probability of continuation. This reduces the number of false signals and makes the movement structure more stable.



Building Historical Data: A Journey into the Past

History generation is one of the most technically complex aspects of the system. It is not enough to simply copy existing bars; they need to be recalculated through the lens of multi-timeframe analysis. The process begins by loading data from the lowest timeframe (M5), as it provides the greatest level of detail.

bool GenerateUnifiedHistory()
{
   datetime endTime = TimeCurrent();
   datetime startTime = endTime - HistoryDays * 24 * 60 * 60;
   
   MqlRates sourceRates[];
   int copied = CopyRates(SourceSymbol, PERIOD_M5, startTime, endTime, sourceRates);
   
   if(copied <= 0)
   {
      Print("Bar copying error: ", GetLastError());
      return false;
   }

   Print("Handling ", copied, " M5 bars for creating a unified Renko");

   ArrayResize(unifiedRates, copied * 2);
   unifiedRatesCount = 0;

   double basePrice = sourceRates[0].close;
   for(int i = 0; i < 4; i++)
   {
      timeframes[i].emaValue = basePrice;
      timeframes[i].lastRenkoPrice = basePrice;
   }

   unifiedLastPrice = basePrice;
   CreateInitialBars(basePrice, sourceRates[0].time);

   for(int i = 0; i < copied; i++)
   {
      ProcessHistoricalBar(sourceRates[i]);
   }

   ArrayResize(unifiedRates, unifiedRatesCount);
   if(!CustomRatesUpdate(UnifiedSymbol, unifiedRates))
   {
      Print("Error updating unified symbol: ", GetLastError());
      return false;
   }

   Print("Created ", unifiedRatesCount, " unified Renko bars");
   return true;
}

The function performs several key steps. First, the time range is defined — from the current time back a specified number of days. Next, the M5 bars are loaded using CopyRates(). The array used to store Renko bars is pre-sized to twice the size of the original data — this is a heuristic estimate; typically, the number of Renko bars turns out to be smaller.

The critical step is initialization. All EMAs start with the same value (the first closing price); otherwise, major artifacts would appear at the beginning of the history due to sharp jumps in uninitialized averages. Two initial bars are created so that the algorithm can correctly determine the direction of movement.

Each historical M5 bar is then passed through the ProcessHistoricalBar() function, which updates the EMAs for all timeframes and generates a composite signal. If the signal is strong enough, a new Renko brick is created. Finally, the array is trimmed to its actual size and passed to CustomRatesUpdate(), which writes the entire history to a virtual symbol in a single operation.



Processing Each Historical Bar: Details Matter

Each M5 bar is processed by a dedicated function that coordinates the updating of all system components.

void ProcessHistoricalBar(const MqlRates &bar)
{
   UpdateTimeframeEMAs(bar.close, bar.time);
   double compositePrice = CalculateCompositeSignal();
   
   if(compositePrice > 0)
   {
      unifiedRatesCount = LoadUnifiedPrice(compositePrice, bar.time);
      unifiedLastBarTime = bar.time;
   }
}

The function appears deceptively simple, but its conciseness masks complex logic. UpdateTimeframeEMAs() checks whether each timeframe needs to be updated at the current time. For example, if the current bar belongs to the same H1 period as the previous one, the H1 EMA is not recalculated, which saves computational resources and ensures the accuracy of the calculations.

CalculateCompositeSignal() applies the selected combination method (simple average, weighted average, or consensus) and returns a synthetic price that represents the collective opinion of all timeframes. If this price is positive (which is always the case for currency pairs, but may not be the case for synthetic indices), it is passed to LoadUnifiedPrice().

LoadUnifiedPrice() is the most complex function in the system. It determines whether a new brick needs to be created and, if so, how many. With a strong impulse, several bricks may be created in a row during a single call. The function also tracks wicks, if enabled, and correctly handles price reversals.



Determining Direction: The Logic of Renko Movement

For each timeframe, the system tracks a virtual Renko chart that exists independently of the unified synthetic symbol.

void UpdateTimeframeRenkoDirection(int tfIndex)
{
   double emaPrice = timeframes[tfIndex].emaValue;
   double lastRenko = timeframes[tfIndex].lastRenkoPrice;
   
   if(emaPrice >= lastRenko + currentBrickSize)
   {
      timeframes[tfIndex].renkoDirection = 1;
      timeframes[tfIndex].lastRenkoPrice = lastRenko + currentBrickSize;
      timeframes[tfIndex].signalStrength = (emaPrice - lastRenko) / currentBrickSize;
   }
   else if(emaPrice <= lastRenko - currentBrickSize)
   {
      timeframes[tfIndex].renkoDirection = -1;
      timeframes[tfIndex].lastRenkoPrice = lastRenko - currentBrickSize;
      timeframes[tfIndex].signalStrength = (lastRenko - emaPrice) / currentBrickSize;
   }
   else
   {
      timeframes[tfIndex].renkoDirection = 0;
      timeframes[tfIndex].signalStrength = 0;
   }
}

This is the standard Renko logic: if the EMA moves above the previous Renko price plus the brick size, an upward movement is recorded. If it falls below the previous Renko price minus the brick size, the movement is downward. If it remains inside the range, it is a sideways market.

The signalStrength field contains additional information — how far the price has moved away from the last brick. A value of 1.5 means that the movement is sufficient for one and a half bricks. This can be used to prioritize timeframes with stronger momentum.

An interesting point: each timeframe has its own Renko price, which does not necessarily match the prices on other timeframes. The M5 chart may show 1.0850, while the H4 chart may show 1.0840. This is normal and reflects different time scales. The synthesis is based on EMA values and directions, rather than on direct price averaging.



Visualization: When a Chart Comes to Life

To monitor the system's operation in real time, a separate window is created for the virtual symbol.

void OpenUnifiedChart()
{
   unifiedChartId = ChartOpen(UnifiedSymbol, PERIOD_M1);
   if(unifiedChartId != 0)
   {
      ChartSetInteger(unifiedChartId, CHART_MODE, CHART_CANDLES);
      ChartSetInteger(unifiedChartId, CHART_AUTOSCROLL, true);
      ChartSetInteger(unifiedChartId, CHART_SCALE, 3);
      
      string comment = "Unified Multi-TF Renko - Brick: " + 
                      DoubleToString(currentBrickSize, _Digits) + 
                      " - Method: " + GetSignalMethodName() + 
                      " - EMA: " + IntegerToString(EMAPeriod);
      ChartSetString(unifiedChartId, CHART_COMMENT, comment);
      
      Print("Unified chart opened. ID: ", unifiedChartId);
   }
}

The chart opens in candlestick mode, although the "candlesticks" themselves are actually Renko bricks with identical bodies. Auto-scroll is enabled so that the chart always displays the most recent data. The zoom level is set to 3 — a medium setting suitable for most screens.

The comment at the top of the chart lists the key parameters: brick size, signal combination method, and EMA period. This lets you quickly see which configuration the system is running with, without having to dig through the settings.

Seeing a chart like this is a real treat. It looks like a hybrid between a classic Renko chart and an aggregated bar stream. Every movement becomes logical, and false momentum bursts disappear. On EURUSD, where a standard M5 chart shakes as if the euro and the dollar had epilepsy, the unified Renko chart shows a clear structure: three bricks up, a pullback, five down, and consolidation.

You can take the visualization a step further by color-coding the bricks based on which timeframe is dominant. For example, if consensus is reached based on the H4 timeframe, the brick is colored dark red or dark blue. If the decision is based on the M5 and M15 timeframes, light shades are used. Then the market structure literally comes to life: H4 colors the global trend, M5 adds “breathing,” and M15 and H1 shape the “rhythm.”



Practical Application: From Theory to Trading

How can you use unified multi-timeframe Renko in real-world trading? Here are a few strategies:

Strategy 1. Trend trading. We wait for a sequence of consecutive bricks to form in one direction — this signals the start of an impulse. We enter in the direction of movement on the fourth brick. Set the stop-loss two bricks back. Set the take-profit at a distance of 5–7 bricks, or take profit when a brick appears in the opposite direction.

Strategy 2. Consolidation breakout. When the chart enters a sideways market (bricks alternate up-down-up-down), we mark the range. On a breakout — a series of 2–3 bricks in one direction — we enter in the direction of the breakout. This is the moment when all timeframes finally come into alignment.

Strategy 3. Correlation arbitrage. We create two unified Renko charts: one for EURUSD and one for AUDUSD. These pairs are usually correlated. If EURUSD is showing a strong uptrend (5 or more bricks up), while AUDUSD is still in a sideways market, there is a high probability that AUDUSD will catch up. We open a long position on AUDUSD with a tight stop.



Expert Advisor Trading

Imagine: your multi-timeframe Renko chart is ready, and the bricks are being created in real time. Now let's make it trade. The RenkoEA_MA_Cross.mq5 Expert Advisor is simple but functional. It identifies the crossover of two MAs on the Renko symbol and opens a position on the real instrument. No noise — just confirmed signals from four timeframes.

The architecture is simple. Two symbols: RenkoSymbol for analysis (e.g., EURUSD_UNIFIED_RENKO) and TradeSymbol for trading (EURUSD). The MAs are calculated from brick closes; PERIOD_CURRENT means Renko bars.

input string RenkoSymbol = "EURUSD_UNIFIED_RENKO"; // Renko symbol (analysis)
input string TradeSymbol = "EURUSD";              // Trading symbol

In OnInit, we add the symbols to Market Watch and create iMA handles on the Renko symbol. The timer waits for ticks from both symbols to synchronize.

   // Add symbols
   if(!AddSymbolToMarket(RenkoSymbol))
     {
      Print("Error: Failed to add ", RenkoSymbol);
      return(INIT_FAILED);
     }
   if(!AddSymbolToMarket(TradeSymbol))
     {
      Print("Error: Failed to add ", TradeSymbol);
      return(INIT_FAILED);
     }

   // Create indicators on the Renko symbol
   fast_ma_handle = iMA(RenkoSymbol, PERIOD_CURRENT, FastMAPeriod, 0, MAMethod, MAPrice);
   slow_ma_handle = iMA(RenkoSymbol, PERIOD_CURRENT, SlowMAPeriod, 0, MAMethod, MAPrice);

   if(fast_ma_handle == INVALID_HANDLE || slow_ma_handle == INVALID_HANDLE)
     {
      Print("Error creating MA indicator");
      return(INIT_FAILED);
     }

   EventSetTimer(1);
   Print("Waiting for ticks from ", RenkoSymbol, " and ", TradeSymbol, "...");
   return(INIT_SUCCEEDED);

In OnTick, we check for a new brick: if(rates[0].time > lastBarTime). Copy 3–4 bars and the MA buffers. The signal is generated when the crossover occurs between the two most recent closed bars and the bar before it (BarShift=1 by default, to avoid catching an unfinished brick).

void OnTick()
  {
   if(!symbols_ready) return;
   if(Symbol() != TradeSymbol) return;

   // Get the bars and MA values
   MqlRates rates[];
   int copied = CopyRates(RenkoSymbol, PERIOD_CURRENT, 0, 4, rates);
   if(copied < 3)
     {
      Print("Error: CopyRates returned ", copied, " bars");
      return;
     }

   // New bar?
   if(rates[0].time <= lastBarTime) return;
   if(BarShift + 1 >= copied) return;

   // Copy the MA values
   double fast_ma[], slow_ma[];
   if(CopyBuffer(fast_ma_handle, 0, 0, 3, fast_ma) <= 0 ||
      CopyBuffer(slow_ma_handle, 0, 0, 3, slow_ma) <= 0)
     {
      Print("Failed to copy MA buffer");
      return;
     }

   int shift = BarShift;

   // Check for a crossover on the previous closed bar (shift)
   bool prev_fast_above = fast_ma[shift + 1] > slow_ma[shift + 1];
   bool prev_fast_below = fast_ma[shift + 1] < slow_ma[shift + 1];
   bool curr_fast_above = fast_ma[shift] > slow_ma[shift];
   bool curr_fast_below = fast_ma[shift] < slow_ma[shift];

   bool buySignal  = prev_fast_below && curr_fast_above;  // Upward crossover
   bool sellSignal = prev_fast_above && curr_fast_below;  // Downward crossover

   // Logging
   Print("=== MA CHECK ===");
   Print("Bar[", shift, "]: FastMA=", DoubleToString(fast_ma[shift], _Digits),
         " SlowMA=", DoubleToString(slow_ma[shift], _Digits));
   Print("Prev: Fast", (prev_fast_above ? ">":"<"), "Slow → Curr: Fast", (curr_fast_above ? ">":"<"), "Slow");
   Print("BUY=", buySignal, " SELL=", sellSignal);

   // --- CLOSE ON SIGNAL ---
   if(OnePosition && PositionsTotalByMagic() > 0 && CloseOnSignal)
     {
      if(buySignal)  ClosePositions(POSITION_TYPE_SELL);
      if(sellSignal) ClosePositions(POSITION_TYPE_BUY);
     }

   // --- OPEN A NEW POSITION ---
   if(OnePosition && PositionsTotalByMagic() > 0) return;

   double price, sl = 0, tp = 0;
   int digits = (int)SymbolInfoInteger(TradeSymbol, SYMBOL_DIGITS);
   double point = SymbolInfoDouble(TradeSymbol, SYMBOL_POINT);

   if(buySignal)
     {
      price = SymbolInfoDouble(TradeSymbol, SYMBOL_ASK);
      if(StopLoss > 0)  sl = NormalizeDouble(price - StopLoss * point, digits);
      if(TakeProfit > 0) tp = NormalizeDouble(price + TakeProfit * point, digits);
      OpenPosition(POSITION_TYPE_BUY, price, sl, tp);
     }

   if(sellSignal)
     {
      price = SymbolInfoDouble(TradeSymbol, SYMBOL_BID);
      if(StopLoss > 0)  sl = NormalizeDouble(price + StopLoss * point, digits);
      if(TakeProfit > 0) tp = NormalizeDouble(price - TakeProfit * point, digits);
      OpenPosition(POSITION_TYPE_SELL, price, sl, tp);
     }

   // Update the time of the last bar
   if(rates[0].time > lastBarTime)
      lastBarTime = rates[0].time;
  }

Buy: the fast MA has crossed above the slow MA. Sell: the fast MA has crossed below. If CloseOnSignal is set, an opposite signal closes the position. OnePosition — only one open position is allowed.

Functions: AddSymbolToMarket — selects a symbol. OpenPosition — builds a request. ClosePositions — iterates over tickets and closes positions by type. CalculateLot — takes the minimum, maximum, and step into account.

Here is my backtest. The following parameters were used; the test was run on EURUSD, using a combination of EURUSD M15 and synthetic EURUSDRENKO M1:

RenkoSymbol=EURUSD_UNIFIED_RENKO
TradeSymbol=EURUSD
FastMAPeriod=300
SlowMAPeriod=400
MAMethod=3
MAPrice=5
Lots=0.1
RiskPercent=7
StopLoss=700
TakeProfit=100
Slippage=30
CloseOnSignal=true
BarShift=1
MagicNumber=123456
OnePosition=true
Comm=RenkoEA

I am quite pleased with the results. The Sharpe ratio is over 15, which is excellent:

The backtest chart also looks good, and there is virtually no equity drawdown:

The code is ready for testing. Run it on Renko historical data; try optimizing it and running it. On a demo account, check the execution. Changing the EMA in Renko or the consensus method makes the signals cleaner.

This is a basic EA, but it is already clear that it generates fewer false signals on synthetic Renko than on regular bars. Experiment with different periods and add your own filters. The market is displayed on a single chart—and the trading robot trades based on it.



Conclusion: A New Language for Market Analysis

This project is not just another indicator or trading bot. This is the concept of a new type of analysis, in which temporal scales cease to be isolated layers of reality and merge into a single whole. By creating a multi-timeframe Renko chart, we are essentially creating a “synthetic market” — a space where all time scales converge on a single chart.

Renko was originally a tool for abstraction, a way to take time out of the equation and focus on pure price movement. The multi-timeframe approach adds another level of abstraction — it eliminates not only time but also the arbitrariness of choosing a timeframe. You no longer have to decide whether to trade on the M15 or H1 timeframe. The system makes the decision for you by weighing the opinions of all timeframes.

This is a philosophy akin to quantum mechanics: observation on a single timeframe collapses the market’s wave function into a specific state. But true reality exists across all timeframes simultaneously, in superposition. Unified Renko is a way to observe this superposition without collapsing it, to see the market as it really is, rather than as it appears over an arbitrarily chosen period.

You can take this idea further: incorporate correlated instruments (gold for AUDUSD, oil for CADJPY), include fundamental indicators (news calendar, interest rates), use neural networks to dynamically adjust weights, or even create ensembles of several unified Renko charts with different parameters, where the final decision is made by voting.

In this context, Renko ceases to be a filter — it becomes an interface between market noise and the trend structure. This is not a visualization, but a language in which price behavior can be described. A language where every word is a brick, every sentence is a sequence of movements, and the entire text is the story of the market, told without unnecessary details but with its full meaning preserved.

The code is open to experimentation. Change the weights, add timeframes, and integrate new indicators. Perhaps it is your modification that will reveal a pattern that has so far remained hidden in the multidimensional space of temporal scales. Good luck exploring the markets!

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/20183

Attached files |
Renko_EMA_4TF.mq5 (44.49 KB)
RenkoEA.mq5 (22.65 KB)
Last comments | Go to discussion (5)
Alexander Lasygin
Alexander Lasygin | 19 Nov 2025 at 03:35

COULD IT BE that a new idea has emerged in this thread? I’ve been using Renko charts for over 10 years myself. I consider them the most promising approach to trading. I’ve found the most successful combination of strategies for myself (in my view): Renko + Volume. And so I decided that was ‘it’ for me. I’ve run out of ideas because the current results are more than effective, and no new ideas are coming to light in either direction. It’s as if everyone’s gone mad over AI. There’s so little that’s new. It’s like trying to build a racing car with a steam engine. The market isn’t about maths; it’s about people. Let’s all get back to the creative process. Thank you so much.

Ilya Shustov
Ilya Shustov | 21 Nov 2025 at 15:51
An article 95 per cent written by neural networks. The volume is off the charts.The claimed back-test results (a Sharpe ratio above 15 with virtually zero drawdown) seem unrealistic even for a synthetic instrument; in reality, such figures are only possible through overfitting or when analysed over a microscopic section of historical data.
Владимир
Владимир | 21 Nov 2025 at 20:16
Ilya Shustov #:
The number is off the charts.
Or maybe it’s auto-correct in the text editor )
Aleksei Kuznetsov
Aleksei Kuznetsov | 22 Nov 2025 at 10:19

As I understand it, the bricks are mainly formed by changes in the EMA on the M5 timeframe.

The others adjust the total price every 15, 60 and 240 minutes.

On the H4 timeframe, the closing price may change by as little as 0.01000. Taking the 20 EMA into account, the price movements will be smaller, for example 0.00100. In that case, on your chart with bricks of 0.00010, 10 bricks will be drawn in one direction when the H4 period begins – simply because the system waited for 4 hours and finally recalculated.

The optimisation may be tuned specifically to these recalculation ‘bricks’, as they occur regularly every 4 hours. However, the actual price will not pass through 0.00100 in a fraction of a second when the TP is triggered. The result will be random, minus the mark-up.

The same applies to H1 and M15, but there will be fewer ‘bricks’ arising from their recalculation.

I think they just introduce distortions. Trading solely on the M5 EMA seems more promising.

If I’ve misunderstood, please clarify.

Stanislav Korotky
Stanislav Korotky | 22 Nov 2025 at 17:54

I found the settings (FastMAPeriod=300, SlowMAPeriod=400) rather odd – there’s a massive time lag between the signals and actual price movements.

Furthermore, with StopLoss=700 and TakeProfit=100, it looks as though the system is holding on to losses for too long.

Features of Custom Indicators Creation Features of Custom Indicators Creation
Creation of Custom Indicators in the MetaTrader trading system has a number of features.
Designing a Unified Order Execution Gateway Class in MQL5 Designing a Unified Order Execution Gateway Class in MQL5
This class provides one point of contact for trade operations in MQL5. It rounds and clamps lot sizes, validates SL/TP against the broker's minimum distance, resolves a compatible filling policy, and applies bounded retries for transient retcodes. Calls return a structured CGatewayResult instead of raw retcodes, simplifying error handling and maintenance across strategies.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
From Novice to Expert: Systematic Profit Conservation Using Candle Range Theory From Novice to Expert: Systematic Profit Conservation Using Candle Range Theory
A hybrid exit engine for MQL5 replaces static TPs with CRT-derived structural levels. The CRT_ProfitConserve class secures a partial at the first level and then trails the remaining position by structural anchors rather than fixed pips. The article walks through the class API, essential methods, and example usage in EAs, providing a clear path to embed CRT-based exits into existing strategies.