preview
Creating Custom Indicators in MQL5 (Part 4): Smart WaveTrend Crossover with Dual Oscillators

Creating Custom Indicators in MQL5 (Part 4): Smart WaveTrend Crossover with Dual Oscillators

MetaTrader 5Trading |
155 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 3), we developed a multi-gauge indicator in MetaQuotes Language 5 (MQL5) with enhancements for sector and round styles, allowing dynamic visualization of multiple data points through customizable gauge displays and color-coded sectors. In Part 4, we develop a Smart WaveTrend Crossover indicator utilizing dual oscillators—one for signals and one for trend filtering—to generate crossover-based buy and sell alerts with optional trend confirmation. This indicator colors candles by trend direction, plots arrow signals on crossovers, and supports customizable parameters for length and visual style. We will cover the following topics:

  1. Understanding the Smart WaveTrend Crossover Framework
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have a functional MQL5 indicator for WaveTrend crossovers, ready for customization—let’s dive in!


Understanding the Smart WaveTrend Crossover Framework

The Smart WaveTrend Crossover framework relies on the WaveTrend oscillator, a momentum-based tool that measures overbought and oversold conditions using smoothed price averages. This helps us identify potential reversals or continuations in market momentum. It computes a source price from highs, lows, and closes, then applies exponential moving averages to create two lines: a faster oscillating line and a slower smoothed line. Crossovers between these lines signal buying or selling opportunities. By using dual WaveTrend configurations—one with shorter periods for sensitive signal generation and another with longer periods for overall trend detection—we can combine quick entry cues with broader market context. This helps filter out false signals in choppy conditions.

In a bullish setup, we look for the faster line to cross above the slower line on the signal oscillator. This indicates building upward momentum, especially when it aligns with an uptrend from the slower oscillator. In a bearish setup, the faster line crossing below the slower line suggests downward momentum. Ideally, this is confirmed by a downtrend on the slower oscillator to avoid counter-trend trades. This approach enables us to capitalize on momentum shifts while respecting the prevailing trend. Additional visuals, such as colored candles, highlight trend direction, and arrows mark precise signal points.

We plan to calculate the WaveTrend values separately for signals and trends using customizable lengths for channels, averages, and moving averages, detect crossovers on the signal side, and apply an optional trend filter to ensure signals match the trend direction. We will incorporate visual elements such as candle coloring based on trend state and offset arrows for buy or sell indications, creating a comprehensive system that provides clear, actionable insights for momentum trading. In brief, here is a visual representation of our objectives.

INDICATOR FRAMEWORK


Implementation in MQL5

To create the indicator in MQL5, just open the MetaEditor, go to the Navigator, locate the Indicators folder, click on the "New" tab, and follow the prompts to create the file. Once it is created, in the coding environment, we will define the indicator properties and settings, such as the number of buffers, plots, and individual line properties, such as the color, width, and label.

//+------------------------------------------------------------------+
//|                           1. Smart WaveTrend Crossover PART1.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#property indicator_chart_window
#property indicator_buffers 23
#property indicator_plots 3
#property indicator_label1 "Colored Candles"
#property indicator_type1 DRAW_COLOR_CANDLES
#property indicator_color1 clrTeal, clrRed
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1
#property indicator_label2 "Buy Signals"
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrForestGreen
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
#property indicator_label3 "Sell Signals"
#property indicator_type3 DRAW_ARROW
#property indicator_color3 clrOrangeRed
#property indicator_style3 STYLE_SOLID
#property indicator_width3 1

We start by configuring the indicator to display directly on the main chart window using "#property indicator_chart_window", ensuring it overlays price data without opening a separate subwindow. We then allocate a total of 23 buffers with "#property indicator_buffers 23" to hold all necessary data arrays for calculations and visuals, and specify 3 plots with "#property indicator_plots 3" to define the visible elements on the chart.

For the first plot, we label it "Colored Candles" and set its type to DRAW_COLOR_CANDLES to render price bars with dynamic colors, using "clrTeal" for bullish and "clrRed" for bearish, styled as solid with a width of 1. The second plot is labeled "Buy Signals" with type "DRAW_ARROW", colored clrForestGreen, solid style, and width 1, to mark potential entry points visually. Similarly, the third plot, labeled "Sell Signals", uses DRAW_ARROW type, "clrOrangeRed" color, solid style, and width 1, for indicating sell opportunities. We now define some inputs to control the indicator.

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "Colors"
input color col_up = clrTeal;                // Bull Color
input color col_dn = clrRed;                 // Bear Color

input group "WaveTrend Settings for Signals"
input int wt_channel_len = 5;                // Signal Channel Length
input int wt_average_len = 10;               // Signal Average Length
input int wt_ma_len = 4;                     // Signal MA Length

input group "WaveTrend Settings for Trend"
input int wt_trend_channel_len = 10;         // Trend Channel Length
input int wt_trend_average_len = 100;        // Trend Average Length
input int wt_trend_ma_len = 10;              // Trend MA Length

input group "Signal Settings"
input bool use_trend_filter = true;          // Use Trend Filter?
input color signal_buy_col = clrForestGreen; // Buy Signal Color
input color signal_sell_col = clrOrangeRed;  // Sell Signal Color
input int base_offset = 10;                  // Base Signal Offset from Candle

input group "Visual Settings"
input bool color_candles = true;             // Color Candles by Trend?

Here, we organize the user inputs into logical groups using "input group" to make the configuration more intuitive in the indicator's settings dialog. First, under the "Colors" group, we allow us to select the color for bullish trends, defaulting to teal, and for bearish trends, defaulting to red, which will be applied to candle coloring. Next, in the "WaveTrend Settings for Signals" group, we provide integer inputs for the signal oscillator's channel length set to 5, average length to 10, and moving average length to 4, enabling customization of the sensitivity for generating crossover signals.

Then, the "WaveTrend Settings for Trend" group includes similar integer inputs but with longer defaults: channel length at 10, average length at 100, and moving average length at 10, tailored for detecting the broader market trend. In the "Signal Settings" group, we include a boolean option enabled by default to use trend filtering, along with color selections for buy signals defaulting to forest green and sell signals to orange-red, plus an integer for the base offset from candles set to 10 points, controlling signal arrow placement. Finally, the "Visual Settings" group offers a boolean input, enabled by default, to color candles based on the detected trend, giving users control over whether to apply dynamic candle visuals. Upon compilation, we get the following input section.

INDICATOR INPUTS

With that, we can now define some global variables for the indicator buffers and helper functions to be used globally.

//+------------------------------------------------------------------+
//| Buffers                                                          |
//+------------------------------------------------------------------+
double esa_signal[];          //--- Signal ESA buffer
double d_signal[];            //--- Signal D buffer
double ci_signal[];           //--- Signal CI buffer
double wt1_signal[];          //--- Signal WT1 buffer
double wt2_signal[];          //--- Signal WT2 buffer
double signal_hist[];         //--- Signal histogram buffer
double signal_bull_cross[];   //--- Signal bull cross buffer
double signal_bear_cross[];   //--- Signal bear cross buffer

double esa_trend[];           //--- Trend ESA buffer
double d_trend[];             //--- Trend D buffer
double ci_trend[];            //--- Trend CI buffer
double wt1_trend[];           //--- Trend WT1 buffer
double wt2_trend[];           //--- Trend WT2 buffer
double trend_hist[];          //--- Trend histogram buffer
double trend_is_bull[];       //--- Trend bull indicator buffer
double trend_is_bear[];       //--- Trend bear indicator buffer

double openBuf[];             //--- Open price buffer
double highBuf[];             //--- High price buffer
double lowBuf[];              //--- Low price buffer
double closeBuf[];            //--- Close price buffer
double candleColorBuf[];      //--- Candle color buffer
double buyArrowBuf[];         //--- Buy arrow buffer
double sellArrowBuf[];        //--- Sell arrow buffer

//+------------------------------------------------------------------+
//| Calculate EMA manually                                           |
//+------------------------------------------------------------------+
double CalcEMA(double prev, double val, int period) {
   if (period < 1) return val;              //--- Handle invalid period
   double alpha = 2.0 / (period + 1);       //--- Compute alpha factor
   return alpha * val + (1 - alpha) * prev; //--- Return EMA value
}

We declare a series of double arrays to serve as buffers for holding intermediate and final data during indicator calculations. These include buffers for the signal WaveTrend components, such as those for exponential smoothing averages, differences, channel indices, and the two main lines used in crossover detection, along with histogram and crossover flags for bullish and bearish signals. Similarly, we set up parallel buffers for the trend WaveTrend, covering its own smoothing, differences, indices, lines, histogram, and boolean flags to identify bull or bear trends. Additionally, we allocate buffers for price data to redraw candles with colors, including open, high, low, close, and a color index buffer, as well as separate buffers for plotting buy and sell arrows at signal points.

To support the WaveTrend computations, we define the "CalcEMA" function, which manually calculates an exponential moving average by first checking for invalid periods and returning the value directly if so, then computing an alpha smoothing factor as 2.0 divided by the period plus one, and finally applying it to blend the current value with the previous EMA for a smoothed result. We will use the function when doing the computations per tick, but for now, let us initialize the indicator using the following approach.

//+------------------------------------------------------------------+
//| Initialize indicator                                             |
//+------------------------------------------------------------------+
int OnInit() {
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);   //--- Set indicator digits
   IndicatorSetString(INDICATOR_SHORTNAME, "Smart WaveTrend Crossover PART1"); //--- Set short name

   SetIndexBuffer(0, openBuf, INDICATOR_DATA);       //--- Bind open buffer
   SetIndexBuffer(1, highBuf, INDICATOR_DATA);       //--- Bind high buffer
   SetIndexBuffer(2, lowBuf, INDICATOR_DATA);        //--- Bind low buffer
   SetIndexBuffer(3, closeBuf, INDICATOR_DATA);      //--- Bind close buffer
   SetIndexBuffer(4, candleColorBuf, INDICATOR_COLOR_INDEX); //--- Bind color buffer
   PlotIndexSetInteger(0, PLOT_SHOW_DATA, color_candles); //--- Set candle visibility

   SetIndexBuffer(5, buyArrowBuf, INDICATOR_DATA);   //--- Bind buy arrow buffer
   SetIndexBuffer(6, sellArrowBuf, INDICATOR_DATA);  //--- Bind sell arrow buffer
   PlotIndexSetInteger(1, PLOT_ARROW, 233);          //--- Set buy arrow symbol
   PlotIndexSetInteger(1, PLOT_SHOW_DATA, true);     //--- Enable buy arrow display
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 0);       //--- Set buy draw begin
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, signal_buy_col); //--- Set buy color

   PlotIndexSetInteger(2, PLOT_ARROW, 234);          //--- Set sell arrow symbol
   PlotIndexSetInteger(2, PLOT_SHOW_DATA, true);     //--- Enable sell arrow display
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, 0);       //--- Set sell draw begin
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, signal_sell_col); //--- Set sell color

   SetIndexBuffer(7, esa_signal, INDICATOR_CALCULATIONS);  //--- Bind signal ESA
   SetIndexBuffer(8, d_signal, INDICATOR_CALCULATIONS);    //--- Bind signal D
   SetIndexBuffer(9, ci_signal, INDICATOR_CALCULATIONS);   //--- Bind signal CI
   SetIndexBuffer(10, wt1_signal, INDICATOR_CALCULATIONS); //--- Bind signal WT1
   SetIndexBuffer(11, wt2_signal, INDICATOR_CALCULATIONS); //--- Bind signal WT2
   SetIndexBuffer(12, signal_hist, INDICATOR_CALCULATIONS); //--- Bind signal hist
   SetIndexBuffer(13, signal_bull_cross, INDICATOR_CALCULATIONS); //--- Bind bull cross
   SetIndexBuffer(14, signal_bear_cross, INDICATOR_CALCULATIONS); //--- Bind bear cross

   SetIndexBuffer(15, esa_trend, INDICATOR_CALCULATIONS); //--- Bind trend ESA
   SetIndexBuffer(16, d_trend, INDICATOR_CALCULATIONS);   //--- Bind trend D
   SetIndexBuffer(17, ci_trend, INDICATOR_CALCULATIONS);  //--- Bind trend CI
   SetIndexBuffer(18, wt1_trend, INDICATOR_CALCULATIONS); //--- Bind trend WT1
   SetIndexBuffer(19, wt2_trend, INDICATOR_CALCULATIONS); //--- Bind trend WT2
   SetIndexBuffer(20, trend_hist, INDICATOR_CALCULATIONS); //--- Bind trend hist
   SetIndexBuffer(21, trend_is_bull, INDICATOR_CALCULATIONS); //--- Bind trend bull
   SetIndexBuffer(22, trend_is_bear, INDICATOR_CALCULATIONS); //--- Bind trend bear

   return(INIT_SUCCEEDED);                                 //--- Return success
}

In the OnInit event handler, we configure the indicator's properties by setting the display digits to match the symbol's precision with IndicatorSetInteger and assigning a short name using IndicatorSetString for easy identification in the platform. We bind the price-related buffers for open, high, low, and close to the initial plot indices as data types, and attach the color buffer as a color index to support dynamic candle rendering, while controlling the plot's visibility via PlotIndexSetInteger based on user preference. For the arrow plots, we link the buy and sell arrow buffers to their respective indices, define arrow symbols with specific codes like 233 for buys and 234 for sells, enable display from the chart's start, and apply custom colors using the "PlotIndexSetInteger" function. You can use any codes of your choosing from the MQL5 Wingdings as below.

MQL5 WINGDINGS CODES

We then associate all calculation buffers for the signal and trend WaveTrend elements—covering smoothing averages, differences, indices, lines, histograms, crossovers, and trend indicators—to higher indices as non-visible calculation data. To wrap up, we return INIT_SUCCEEDED to signal that initialization completed without issues. Now, we just need to do our computations on every tick when needed. First, we will initialize the buffers for all the bars if it is the first time we are loading the indicator for all the candles, and then later on, do the necessary computations.

//+------------------------------------------------------------------+
//| Calculate indicator values                                       |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[]) {
   int start = prev_calculated - 1;               //--- Set start index
   if (start < 0) start = 0;                      //--- Adjust invalid start

   if (prev_calculated == 0) {                    //--- Handle initial calculation
      ArrayInitialize(esa_signal, EMPTY_VALUE);   //--- Init signal ESA
      ArrayInitialize(d_signal, EMPTY_VALUE);     //--- Init signal D
      ArrayInitialize(ci_signal, EMPTY_VALUE);    //--- Init signal CI
      ArrayInitialize(wt1_signal, EMPTY_VALUE);   //--- Init signal WT1
      ArrayInitialize(wt2_signal, EMPTY_VALUE);   //--- Init signal WT2
      ArrayInitialize(signal_hist, EMPTY_VALUE);  //--- Init signal hist
      ArrayInitialize(signal_bull_cross, 0);      //--- Init bull cross
      ArrayInitialize(signal_bear_cross, 0);      //--- Init bear cross

      ArrayInitialize(esa_trend, EMPTY_VALUE);    //--- Init trend ESA
      ArrayInitialize(d_trend, EMPTY_VALUE);      //--- Init trend D
      ArrayInitialize(ci_trend, EMPTY_VALUE);     //--- Init trend CI
      ArrayInitialize(wt1_trend, EMPTY_VALUE);    //--- Init trend WT1
      ArrayInitialize(wt2_trend, EMPTY_VALUE);    //--- Init trend WT2
      ArrayInitialize(trend_hist, EMPTY_VALUE);   //--- Init trend hist
      ArrayInitialize(trend_is_bull, 0);          //--- Init trend bull
      ArrayInitialize(trend_is_bear, 0);          //--- Init trend bear

      ArrayInitialize(buyArrowBuf, EMPTY_VALUE);  //--- Init buy arrows
      ArrayInitialize(sellArrowBuf, EMPTY_VALUE); //--- Init sell arrows
   }

   return(rates_total);                           //--- Return total rates
}

In the OnCalculate event handler, we receive parameters including the total number of bars, previously calculated bars, and arrays for time, prices, volumes, and spreads to process new data efficiently. We determine the starting index for calculations by setting it to one less than the previously calculated value, and adjust it to zero if it falls below zero to avoid invalid indexing.

When the indicator is first loaded or recalculated from scratch, indicated by prev_calculated being zero, we initialize all buffers using ArrayInitialize: setting signal and trend component buffers like those for exponential smoothing, differences, indices, lines, histograms to EMPTY_VALUE, crossover and trend flag buffers to zero, and arrow buffers to "EMPTY_VALUE" for a clean start. Finally, we return the total number of rates to signal the end of this calculation cycle. We can now loop via the bars and do our computations. Here is the approach we used to achieve that.

for (int i = start; i < rates_total; i++) {   //--- Loop through bars
   double src_wt = (high[i] + low[i] + close[i]) / 3.0; //--- Compute source

   if (i == 0) {                               //--- Handle first bar signal
      esa_signal[i] = src_wt;                  //--- Set initial ESA
      d_signal[i] = MathAbs(src_wt - esa_signal[i]); //--- Set initial D
      double denom_signal = 0.015 * d_signal[i]; //--- Compute denominator
      ci_signal[i] = (denom_signal != 0.0) ? (src_wt - esa_signal[i]) / denom_signal : 0.0; //--- Set CI
      wt1_signal[i] = ci_signal[i];            //--- Set initial WT1
      wt2_signal[i] = wt1_signal[i];           //--- Set initial WT2
   } else {                                    //--- Handle subsequent bars
      esa_signal[i] = CalcEMA(esa_signal[i-1], src_wt, wt_channel_len); //--- Update ESA
      d_signal[i] = CalcEMA(d_signal[i-1], MathAbs(src_wt - esa_signal[i]), wt_channel_len); //--- Update D
      double denom_signal = 0.015 * d_signal[i]; //--- Compute denominator
      ci_signal[i] = (denom_signal != 0.0) ? (src_wt - esa_signal[i]) / denom_signal : 0.0; //--- Update CI
      wt1_signal[i] = CalcEMA(wt1_signal[i-1], ci_signal[i], wt_average_len); //--- Update WT1
      wt2_signal[i] = 0;                       //--- Reset WT2
      int cnt_ma = 0;                          //--- Init MA count
      for (int k = 0; k < wt_ma_len; k++) {    //--- Loop for MA
         if (i - k < 0) break;                 //--- Skip invalid index
         wt2_signal[i] += wt1_signal[i - k];   //--- Accumulate WT1
         cnt_ma++;                             //--- Increment count
      }
      if (cnt_ma > 0) wt2_signal[i] /= cnt_ma; //--- Average WT2
   }

   signal_hist[i] = wt1_signal[i] - wt2_signal[i]; //--- Compute signal hist
   signal_bull_cross[i] = (wt1_signal[i] > wt2_signal[i] && (i == 0 || wt1_signal[i-1] <= wt2_signal[i-1])) ? 1 : 0; //--- Detect bull cross
   signal_bear_cross[i] = (wt1_signal[i] < wt2_signal[i] && (i == 0 || wt1_signal[i-1] >= wt2_signal[i-1])) ? 1 : 0; //--- Detect bear cross

   double src_wt_trend = src_wt;              //--- Set trend source
   if (i == 0) {                              //--- Handle first bar trend
      esa_trend[i] = src_wt_trend;            //--- Set initial ESA
      d_trend[i] = MathAbs(src_wt_trend - esa_trend[i]); //--- Set initial D
      double denom_trend = 0.015 * d_trend[i]; //--- Compute denominator
      ci_trend[i] = (denom_trend != 0.0) ? (src_wt_trend - esa_trend[i]) / denom_trend : 0.0; //--- Set CI
      wt1_trend[i] = ci_trend[i];             //--- Set initial WT1
      wt2_trend[i] = wt1_trend[i];            //--- Set initial WT2
   } else {                                   //--- Handle subsequent bars
      esa_trend[i] = CalcEMA(esa_trend[i-1], src_wt_trend, wt_trend_channel_len); //--- Update ESA
      d_trend[i] = CalcEMA(d_trend[i-1], MathAbs(src_wt_trend - esa_trend[i]), wt_trend_channel_len); //--- Update D
      double denom_trend = 0.015 * d_trend[i]; //--- Compute denominator
      ci_trend[i] = (denom_trend != 0.0) ? (src_wt_trend - esa_trend[i]) / denom_trend : 0.0; //--- Update CI
      wt1_trend[i] = CalcEMA(wt1_trend[i-1], ci_trend[i], wt_trend_average_len); //--- Update WT1
      wt2_trend[i] = 0;                       //--- Reset WT2
      int cnt_ma_trend = 0;                   //--- Init MA count
      for (int k = 0; k < wt_trend_ma_len; k++) { //--- Loop for MA
         if (i - k < 0) break;                //--- Skip invalid index
         wt2_trend[i] += wt1_trend[i - k];    //--- Accumulate WT1
         cnt_ma_trend++;                      //--- Increment count
      }
      if (cnt_ma_trend > 0) wt2_trend[i] /= cnt_ma_trend; //--- Average WT2
   }

   trend_hist[i] = wt1_trend[i] - wt2_trend[i]; //--- Compute trend hist
   trend_is_bull[i] = (wt1_trend[i] > wt2_trend[i]) ? 1 : 0; //--- Set bull trend
   trend_is_bear[i] = (wt1_trend[i] < wt2_trend[i]) ? 1 : 0; //--- Set bear trend

   if (color_candles) {                       //--- Check candle coloring
      openBuf[i] = open[i];                   //--- Set open
      highBuf[i] = high[i];                   //--- Set high
      lowBuf[i] = low[i];                     //--- Set low
      closeBuf[i] = close[i];                 //--- Set close
      candleColorBuf[i] = trend_is_bull[i] == 1 ? 0 : 1; //--- Set color index
   }

   buyArrowBuf[i] = EMPTY_VALUE;              //--- Reset buy arrow
   sellArrowBuf[i] = EMPTY_VALUE;             //--- Reset sell arrow
   if (signal_bull_cross[i] == 1 && (!use_trend_filter || trend_is_bull[i] == 1)) { //--- Check buy condition
      buyArrowBuf[i] = low[i] - _Point * base_offset; //--- Place buy arrow
   }
   if (signal_bear_cross[i] == 1 && (!use_trend_filter || trend_is_bear[i] == 1)) { //--- Check sell condition
      sellArrowBuf[i] = high[i] + _Point * base_offset; //--- Place sell arrow
   }
}

Here, we loop through each bar from the starting index to the total rates available, calculating the source price as the average of the high, low, and close for that bar to serve as the input for both WaveTrend oscillators.

For the signal WaveTrend, on the first bar we initialize the exponential smoothing average directly from the source, compute the absolute difference for the deviation buffer using the MathAbs function, derive a denominator by multiplying the deviation by 0.015, and set the channel index by dividing the difference between source and smoothing average by the denominator if non-zero, otherwise zero; we then set both main lines to the channel index value. For subsequent bars, we update the exponential smoothing average using "CalcEMA" with the previous value, source, and channel length, refresh the deviation with another "CalcEMA" on the absolute difference from "MathAbs" and same length, recalculate the denominator, update the channel index similarly, smooth the first main line with "CalcEMA" using the average length, and compute the second main line by averaging the recent first line values over the moving average length via a nested loop that accumulates and divides while skipping invalid indices.

We then derive the signal histogram as the difference between the two main signal lines, flag a bullish crossover if the first line is above the second and was not in the prior bar (or on the first bar), and flag a bearish crossover if the first is below the second and was not previously. Repeating a similar process for the trend WaveTrend using the same source, we handle the first bar by initializing its exponential smoothing, deviation with "MathAbs", denominator, channel index, and both main lines identically, but with trend-specific parameters for subsequent bars: updating via "CalcEMA" with trend channel and average lengths, and averaging the second line over the trend moving average length in a nested loop. We compute the trend histogram as the difference between its main lines, set the bull trend flag to 1 if the first line is above the second, and the bear trend flag to 1 if below.

If candle coloring is enabled, we copy the bar's open, high, low, and close to their buffers and assign a color index of 0 for bull trends or 1 for bear trends. Finally, we reset the buy and sell arrow buffers to EMPTY_VALUE, then place a buy arrow below the low by the base offset multiplied by _Point if a bullish signal cross occurred and either no trend filter is used or the trend is bull, and similarly place a sell arrow above the high if a bearish cross happened with matching conditions. Upon compilation, we get the following outcome.

FINAL TEST GIF

From the visualization, we can see that we calculate the indicator, draw the colored candles, and set the indicator buffers, hence achieving our objectives. The thing that remains is backtesting the program, and that is handled in the next section.


Backtesting

We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.

BACKTEST GIF


Conclusion

In conclusion, we’ve developed a Smart WaveTrend Crossover indicator in MQL5 that employs dual WaveTrend oscillators—one for crossover signals and another for trend filtering—with customizable parameters for channel, average, and moving average lengths. The indicator applies trend-based coloring to candles, generates buy and sell arrow signals on crossovers, and incorporates options for trend confirmation along with visual adjustments like colors and offsets. This configuration delivers visual indicators for momentum shifts aligned with broader trends.

In the next part, we will enhance the indicator with advanced visual elements, including signal boxes, fog overlays for trend visualization, customizable buy/sell labels, and integrated take-profit and stop-loss displays for better risk management, as shown below. Stay tuned.

ADVANCEMENT COMPARISON FEATURES

Introduction to MQL5 (Part 34): Mastering API and WebRequest Function in MQL5 (VIII) Introduction to MQL5 (Part 34): Mastering API and WebRequest Function in MQL5 (VIII)
In this article, you will learn how to create an interactive control panel in MetaTrader 5. We cover the basics of adding input fields, action buttons, and labels to display text. Using a project-based approach, you will see how to set up a panel where users can type messages and eventually display server responses from an API.
Fibonacci in Forex (Part I): Examining the Price-Time Relationship Fibonacci in Forex (Part I): Examining the Price-Time Relationship
How does the market observe Fibonacci-based relationships? This sequence, where each subsequent number is equal to the sum of the two previous ones (1, 1, 2, 3, 5, 8, 13, 21...), not only describes the growth of the rabbit population. We will consider the Pythagorean hypothesis that everything in the world is subject to certain relationships of numbers...
Forex arbitrage trading: A simple synthetic market maker bot to get started Forex arbitrage trading: A simple synthetic market maker bot to get started
Today we will take a look at my first arbitrage robot — a liquidity provider (if you can call it that) for synthetic assets. Currently, this bot is successfully operating as a module in a large machine learning system, but I pulled up an old Forex arbitrage robot from the cloud, so let's take a look at it and think about what we can do with it today.
Neural Networks in Trading: Two-Dimensional Connection Space Models (Chimera) Neural Networks in Trading: Two-Dimensional Connection Space Models (Chimera)
In this article, we will explore the innovative Chimera framework: a two-dimensional state-space model that uses neural networks to analyze multivariate time series. This method offers high accuracy with low computational cost, outperforming traditional approaches and Transformer architectures.