🚀 AI One-Click Generation of mql4 Strategy Code

Mql4 Tutorial: How to Create Adaptive Indicators with Dynamic Levels using IndicatorSetDouble()

mql4 | Published on: 2025/12/8
The following content is generated by EasyQuant .

Problem Description

How do I programmatically change the levels of a custom indicator based on market volatility?

Solution

To programmatically change the horizontal levels of a custom indicator in MQL4, you must use the IndicatorSetDouble function with the INDICATOR_LEVELVALUE property identifier.

It is important to note that Indicator Levels (the horizontal lines drawn across the subwindow) are global for the indicator instance. If you change a level based on the current volatility, the line will move up or down for the entire chart history, not just the current bar.

Below is a complete, compilable example of a Dynamic RSI indicator. It calculates the Standard Deviation of the RSI to measure volatility and adjusts the overbought/oversold levels in real-time.

MQL4 Code: Dynamic Levels Indicator

//+------------------------------------------------------------------+
//|                                         DynamicVolatilityRSI.mq4 |
//|                                     Copyright 2023, MetaQuotes   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative AI"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Indicator Settings
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2

//--- Define initial levels (will be overwritten programmatically)
#property indicator_level1 30.0
#property indicator_level2 70.0
#property indicator_levelcolor clrGray
#property indicator_levelstyle STYLE_DOT

//--- Input Parameters
input int      InpRsiPeriod   = 14;       // RSI Period
input int      InpVolPeriod   = 20;       // Volatility Calculation Period
input double   InpDeviations  = 2.0;      // Standard Deviations for Levels
input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; // Applied Price

//--- Indicator Buffers
double RsiBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Indicator mapping
   SetIndexBuffer(0, RsiBuffer);
   SetIndexStyle(0, DRAW_LINE);
   
   //--- Set name and digits
   string short_name = "DynRSI(" + IntegerToString(InpRsiPeriod) + ")";
   IndicatorShortName(short_name);
   IndicatorDigits(2);
   
   //--- Set the number of levels explicitly (0 and 1)
   IndicatorSetInteger(INDICATOR_LEVELS, 2);
   
   //--- Set descriptions for levels
   IndicatorSetString(INDICATOR_LEVELTEXT, 0, "Oversold (Dyn)");
   IndicatorSetString(INDICATOR_LEVELTEXT, 1, "Overbought (Dyn)");

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
   //--- Check for sufficient data
   if(rates_total < InpRsiPeriod + InpVolPeriod)
      return(0);

   //--- Calculation loop
   int limit = rates_total - prev_calculated;
   if(prev_calculated > 0) limit++;

   for(int i = 0; i < limit; i++)
   {
      // 1. Calculate RSI
      RsiBuffer[i] = iRSI(NULL, 0, InpRsiPeriod, InpPrice, i);
   }

   //--- DYNAMIC LEVEL UPDATE LOGIC ---------------------------------+
   // We calculate the volatility of the RSI itself (Standard Deviation)
   // based on the most recent closed bar (index 0 or 1 depending on strategy).
   // Here we use index 0 for real-time updates.
   
   double sum = 0.0;
   double sum_sq = 0.0;
   
   // Calculate Mean and Standard Deviation of RSI over VolPeriod
   for(int k = 0; k < InpVolPeriod; k++)
   {
      double val = iRSI(NULL, 0, InpRsiPeriod, InpPrice, k);
      sum += val;
      sum_sq += val * val;
   }
   
   double mean = sum / InpVolPeriod;
   double variance = (sum_sq / InpVolPeriod) - (mean * mean);
   double std_dev = MathSqrt(variance);
   
   // Calculate new dynamic levels centered around 50 (RSI Midpoint)
   // or centered around the moving average of RSI (mean).
   // Here we center around 50 for classic RSI behavior.
   double new_upper_level = 50.0 + (std_dev * InpDeviations);
   double new_lower_level = 50.0 - (std_dev * InpDeviations);
   
   // Clamp values to keep them within RSI bounds (0-100)
   if(new_upper_level > 99) new_upper_level = 99;
   if(new_lower_level < 1)  new_lower_level = 1;

   //--- UPDATE THE LEVELS PROGRAMMATICALLY -------------------------+
   // Level index 0 is the first level defined (originally 30)
   // Level index 1 is the second level defined (originally 70)
   
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, new_lower_level);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, new_upper_level);
   
   // Optional: Update text to show current value
   IndicatorSetString(INDICATOR_LEVELTEXT, 0, "Low: " + DoubleToString(new_lower_level, 1));
   IndicatorSetString(INDICATOR_LEVELTEXT, 1, "High: " + DoubleToString(new_upper_level, 1));
   //----------------------------------------------------------------+

   return(rates_total);
}
//+------------------------------------------------------------------+

Key Functions Explained

  1. IndicatorSetInteger(INDICATOR_LEVELS, 2):
    This ensures the indicator knows exactly how many horizontal lines to manage. This is usually done in OnInit.

  2. IndicatorSetDouble(INDICATOR_LEVELVALUE, index, value):
    This is the core function used inside OnCalculate.

    • INDICATOR_LEVELVALUE: The property identifier telling MQL4 you want to change the value of a level.
    • index: The zero-based index of the level. If you defined #property indicator_level1, its index is 0. #property indicator_level2 is index 1.
    • value: The new double value (e.g., the calculated volatility-adjusted price).
  3. Volatility Logic:
    In the example above, I calculated the Standard Deviation of the RSI array itself over the last InpVolPeriod bars.

    • If the market is volatile, the Standard Deviation increases, pushing the levels outward (e.g., to 80/20).
    • If the market is flat, the Standard Deviation decreases, pulling the levels inward (e.g., to 60/40).

Important Limitation

As mentioned, IndicatorSetDouble moves the horizontal line for the entire chart. If you look back at history, the lines will be at the position calculated for the current moment, not where they were in the past.

If you need to see the historical progression of dynamic levels (to backtest visually), you should not use "Levels". Instead, you should use Indicator Buffers (DRAW_LINE) to draw the upper and lower bounds.

Q&A

Q: Can I add a new level dynamically if it wasn't defined in properties?
A: Yes. You can use IndicatorSetInteger(INDICATOR_LEVELS, n) in OnInit to define n levels, even if you didn't use #property indicator_levelN. However, you must then set the style, color, and initial values for all of them programmatically.

Q: Why do the levels jump around on the chart?
A: Because the calculation is happening on every tick (or every bar). If the volatility changes slightly, the function updates the global level position immediately. To prevent jitter, you might want to round the values or only update on a new bar event.

Q: How do I access these dynamic levels from an Expert Advisor?
A: You cannot access the "Levels" of a custom indicator directly via iCustom. An EA using iCustom only reads the Indicator Buffers (the data lines). If you need the EA to trade based on these dynamic levels, you must either:

  1. Replicate the volatility math inside the EA.
  2. Convert the Levels into Indicator Buffers (using SetIndexBuffer) so iCustom can read them as mode 1 and 2.