MQL4/.github/instructions/mql4-indicators.instructions.md
Rahul Dhangar 740e967470 Add comprehensive MQL4 instruction files and update main copilot instructions
- Add mql4-syntax-critical.instructions.md: Complete syntax rules for MQL4
- Add mql4-experts.instructions.md: Expert Advisor development guidelines
- Add mql4-indicators.instructions.md: Custom indicator development guidelines
- Add mql4-scripts.instructions.md: Script development guidelines
- Update copilot-instructions.md: Add language-specific instruction routing
- Include MQL4 vs MQL5 comparison table and workflow guidance
- Based on real-world experience from MultiTimeframeZone EA development
2025-11-04 04:07:37 +05:30

16 KiB

applyTo
**/MQL4/Indicators/_Thivyam/**/*.mq4

Instructions for _Thivyam MQL4 Custom Indicators

CRITICAL: MQL4 Syntax Compliance

Before writing any MQL4 code, read and follow: mql4-syntax-critical.instructions.md

Key mandatory rules for indicators:

  • Declare loop variables BEFORE for-loop: int i; for(i=0; i<n; i++)
  • Use #property indicator_* directives correctly
  • Declare indicator buffers properly with SetIndexBuffer()
  • Use IndicatorBuffers() to specify total buffers
  • Always validate buffer indices before accessing

Indicator Structure

Required Property Directives

//+------------------------------------------------------------------+
//|                                         YourIndicatorName.mq4    |
//|                                            Author: Your Name     |
//|                                     https://www.yourwebsite.com  |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property link      "https://www.yourwebsite.com"
#property version   "1.00"
#property strict
#property description "Brief description of indicator"

// Indicator window type
#property indicator_chart_window     // Main chart window
// OR
#property indicator_separate_window  // Separate window below chart

// For separate window indicators
#property indicator_minimum -100     // Minimum value
#property indicator_maximum 100      // Maximum value

// Number of plot buffers (visible lines)
#property indicator_buffers 2

// Plot properties
#property indicator_color1 clrBlue   // First line color
#property indicator_width1 2         // First line width
#property indicator_label1 "Signal"  // First line label

#property indicator_color2 clrRed
#property indicator_width2 1
#property indicator_label2 "Threshold"

Buffer Declaration

Indicator Buffers vs Plot Buffers

  • Plot Buffers: Visible on chart (specified by #property indicator_buffers)
  • Total Buffers: Plot buffers + calculation buffers (set with IndicatorBuffers())

Example: Moving Average Indicator

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrRed
#property indicator_width1 2

// Input parameters
input int InpMAPeriod = 20;          // MA Period
input int InpMAMethod = MODE_SMA;    // MA Method
input int InpMAAppliedPrice = PRICE_CLOSE;  // Applied Price

// Indicator buffers
double BufferMA[];  // Visible buffer

//+------------------------------------------------------------------+
int OnInit()
{
   // Set total buffers (1 plot + 0 calculation)
   IndicatorBuffers(1);
   
   // Bind buffer to indicator buffer array
   SetIndexBuffer(0, BufferMA);
   
   // Set plot style
   SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2, clrRed);
   
   // Set indicator label
   SetIndexLabel(0, "MA(" + IntegerToString(InpMAPeriod) + ")");
   
   // Set empty value (gaps in line)
   SetIndexEmptyValue(0, 0.0);
   
   // Set indicator short name
   IndicatorShortName("MA(" + IntegerToString(InpMAPeriod) + ")");
   
   // Set precision
   IndicatorDigits(Digits);
   
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
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[])
{
   // Calculate start position
   int limit = rates_total - prev_calculated;
   if(prev_calculated > 0) limit++;
   
   // Calculate MA
   int i;
   for(i = 0; i < limit; i++)
   {
      BufferMA[i] = iMA(NULL, 0, InpMAPeriod, 0, InpMAMethod, InpMAAppliedPrice, i);
   }
   
   return rates_total;
}

OnCalculate() Function

Function Signature (Time Series Version)

int OnCalculate(const int rates_total,       // Total bars available
                const int prev_calculated,   // Bars calculated on previous call
                const datetime &time[],      // Time array
                const double &open[],        // Open prices
                const double &high[],        // High prices
                const double &low[],         // Low prices
                const double &close[],       // Close prices
                const long &tick_volume[],   // Tick volumes
                const long &volume[],        // Real volumes
                const int &spread[])         // Spreads
{
   // Calculation logic here
   return rates_total;  // Return total bars processed
}

Efficient Calculation Pattern

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[])
{
   // Calculate how many bars need recalculation
   int limit;
   
   if(prev_calculated == 0)
   {
      // First calculation - process all bars
      limit = rates_total - 1;
      
      // Initialize buffers
      ArrayInitialize(BufferMA, 0);
   }
   else
   {
      // Subsequent calculations - process new bars only
      limit = rates_total - prev_calculated;
   }
   
   // Process bars from oldest to newest
   int i;
   for(i = limit; i >= 0; i--)
   {
      // Calculate indicator value for bar i
      BufferMA[i] = CalculateValue(i);
   }
   
   // Return value for next call
   return rates_total;
}

Drawing Styles

Available Draw Styles

DRAW_LINE          // Line connecting points
DRAW_SECTION       // Sections (gaps at empty values)
DRAW_HISTOGRAM     // Histogram from zero line
DRAW_ARROW         // Arrows at points
DRAW_ZIGZAG        // ZigZag line
DRAW_NONE          // No drawing (calculation buffer)

Example: Multi-Buffer Indicator

#property indicator_separate_window
#property indicator_buffers 3
#property indicator_color1 clrGreen
#property indicator_color2 clrRed
#property indicator_color3 clrGray

// Buffers
double BufferUp[];      // Bullish signal
double BufferDown[];    // Bearish signal
double BufferZero[];    // Zero line

int OnInit()
{
   IndicatorBuffers(3);
   
   // Buffer 0: Bullish histogram
   SetIndexBuffer(0, BufferUp);
   SetIndexStyle(0, DRAW_HISTOGRAM, STYLE_SOLID, 2, clrGreen);
   SetIndexLabel(0, "Bullish");
   
   // Buffer 1: Bearish histogram
   SetIndexBuffer(1, BufferDown);
   SetIndexStyle(1, DRAW_HISTOGRAM, STYLE_SOLID, 2, clrRed);
   SetIndexLabel(1, "Bearish");
   
   // Buffer 2: Zero line
   SetIndexBuffer(2, BufferZero);
   SetIndexStyle(2, DRAW_LINE, STYLE_DOT, 1, clrGray);
   SetIndexLabel(2, "Zero");
   
   IndicatorShortName("Multi Signal");
   
   return INIT_SUCCEEDED;
}

Arrow Drawing

Using Arrow Symbols

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 clrGreen
#property indicator_color2 clrRed

double BufferBuy[];   // Buy arrows
double BufferSell[];  // Sell arrows

int OnInit()
{
   IndicatorBuffers(2);
   
   // Buy arrows (up arrow)
   SetIndexBuffer(0, BufferBuy);
   SetIndexStyle(0, DRAW_ARROW);
   SetIndexArrow(0, 233);  // Up arrow symbol code
   SetIndexLabel(0, "Buy Signal");
   SetIndexEmptyValue(0, 0.0);
   
   // Sell arrows (down arrow)
   SetIndexBuffer(1, BufferSell);
   SetIndexStyle(1, DRAW_ARROW);
   SetIndexArrow(1, 234);  // Down arrow symbol code
   SetIndexLabel(1, "Sell Signal");
   SetIndexEmptyValue(1, 0.0);
   
   return INIT_SUCCEEDED;
}

int OnCalculate(/* ... */)
{
   int i;
   for(i = limit; i >= 0; i--)
   {
      // Place buy arrow below candle low
      if(IsBuySignal(i))
      {
         BufferBuy[i] = low[i] - 10 * Point;
      }
      else
      {
         BufferBuy[i] = 0;  // Empty value (no arrow)
      }
      
      // Place sell arrow above candle high
      if(IsSellSignal(i))
      {
         BufferSell[i] = high[i] + 10 * Point;
      }
      else
      {
         BufferSell[i] = 0;
      }
   }
   
   return rates_total;
}

Common Arrow Codes

233 - Up arrow
234 - Down arrow
159 - Circle
158 - Square
168 - Triangle up
170 - Triangle down

Multi-Timeframe Indicators

Getting Data from Other Timeframes

int OnCalculate(/* ... */)
{
   int i;
   for(i = limit; i >= 0; i--)
   {
      // Get H4 data while indicator is on M15 chart
      double h4High = iHigh(Symbol(), PERIOD_H4, iBarShift(Symbol(), PERIOD_H4, time[i]));
      double h4Low = iLow(Symbol(), PERIOD_H4, iBarShift(Symbol(), PERIOD_H4, time[i]));
      
      // Use H4 data for calculation
      BufferSignal[i] = CalculateFromMTF(h4High, h4Low, close[i]);
   }
   
   return rates_total;
}

Using Other Indicators

Calling Built-in Indicators

int OnCalculate(/* ... */)
{
   int i;
   for(i = limit; i >= 0; i--)
   {
      // Get Moving Average value
      double ma = iMA(Symbol(), PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE, i);
      
      // Get RSI value
      double rsi = iRSI(Symbol(), PERIOD_CURRENT, 14, PRICE_CLOSE, i);
      
      // Get Stochastic values
      double stochMain = iStochastic(Symbol(), PERIOD_CURRENT, 5, 3, 3, MODE_SMA, 0, MODE_MAIN, i);
      double stochSignal = iStochastic(Symbol(), PERIOD_CURRENT, 5, 3, 3, MODE_SMA, 0, MODE_SIGNAL, i);
      
      // Use values for calculation
      BufferSignal[i] = (close[i] > ma && rsi > 50) ? 1 : -1;
   }
   
   return rates_total;
}

MA Method Constants

MODE_SMA  = 0  // Simple Moving Average
MODE_EMA  = 1  // Exponential Moving Average
MODE_SMMA = 2  // Smoothed Moving Average
MODE_LWMA = 3  // Linear Weighted Moving Average

Applied Price Constants

PRICE_CLOSE    = 0  // Close price
PRICE_OPEN     = 1  // Open price
PRICE_HIGH     = 2  // High price
PRICE_LOW      = 3  // Low price
PRICE_MEDIAN   = 4  // (High + Low) / 2
PRICE_TYPICAL  = 5  // (High + Low + Close) / 3
PRICE_WEIGHTED = 6  // (High + Low + Close + Close) / 4

Zone Detection Indicator Example

Supply/Demand Zone Indicator

#property indicator_chart_window
#property indicator_buffers 0  // No plot buffers (we'll draw objects)

input int InpZoneBars = 3;          // Bars to form zone
input color InpDemandColor = clrGreen;
input color InpSupplyColor = clrRed;

datetime g_lastBarTime = 0;

int OnInit()
{
   return INIT_SUCCEEDED;
}

int OnCalculate(/* ... */)
{
   // Detect new bar
   if(time[0] != g_lastBarTime)
   {
      g_lastBarTime = time[0];
      
      // Check for zone patterns
      DetectZones();
   }
   
   return rates_total;
}

void DetectZones()
{
   // Scan recent bars for demand zones
   int i;
   for(i = 1; i < 50; i++)
   {
      if(IsDemandZone(i))
      {
         DrawZone("Demand_" + IntegerToString(iTime(Symbol(), PERIOD_CURRENT, i)),
                  iTime(Symbol(), PERIOD_CURRENT, i),
                  iHigh(Symbol(), PERIOD_CURRENT, i),
                  iLow(Symbol(), PERIOD_CURRENT, i),
                  InpDemandColor);
      }
      
      if(IsSupplyZone(i))
      {
         DrawZone("Supply_" + IntegerToString(iTime(Symbol(), PERIOD_CURRENT, i)),
                  iTime(Symbol(), PERIOD_CURRENT, i),
                  iHigh(Symbol(), PERIOD_CURRENT, i),
                  iLow(Symbol(), PERIOD_CURRENT, i),
                  InpSupplyColor);
      }
   }
}

void DrawZone(string name, datetime time, double high, double low, color zoneColor)
{
   if(ObjectFind(0, name) >= 0) return;  // Already drawn
   
   datetime time2 = time + PeriodSeconds() * 100;
   
   ObjectCreate(0, name, OBJ_RECTANGLE, 0, time, high, time2, low);
   ObjectSetInteger(0, name, OBJPROP_COLOR, zoneColor);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
}

Alerts and Notifications

Alert Implementation

datetime g_lastAlertTime = 0;

int OnCalculate(/* ... */)
{
   // Check for signal on most recent bar
   if(IsSignal(0) && time[0] != g_lastAlertTime)
   {
      g_lastAlertTime = time[0];
      
      // Sound alert
      Alert("Signal detected on ", Symbol(), " ", PeriodToString());
      
      // Email notification (if enabled)
      if(InpSendEmail)
      {
         SendMail("Indicator Alert: " + Symbol(),
                  "Signal detected at " + TimeToString(TimeCurrent()));
      }
      
      // Push notification (if enabled)
      if(InpSendPush)
      {
         SendNotification("Signal: " + Symbol() + " " + PeriodToString());
      }
   }
   
   return rates_total;
}

string PeriodToString()
{
   switch(Period())
   {
      case PERIOD_M1:  return "M1";
      case PERIOD_M5:  return "M5";
      case PERIOD_M15: return "M15";
      case PERIOD_M30: return "M30";
      case PERIOD_H1:  return "H1";
      case PERIOD_H4:  return "H4";
      case PERIOD_D1:  return "D1";
      default:         return "Unknown";
   }
}

Best Practices for Indicators

Performance Optimization

  1. Minimize calculations: Only recalculate changed bars
  2. Use efficient algorithms: Avoid nested loops where possible
  3. Cache values: Store frequently used calculations
  4. Avoid unnecessary repaints: Use prev_calculated correctly

Repaint vs Non-Repaint

// NON-REPAINT: Use closed bar data
int OnCalculate(/* ... */)
{
   // Calculate from bar 1 onwards (skip bar 0)
   int i;
   for(i = limit; i >= 1; i--)  // Start from 1, not 0
   {
      BufferSignal[i] = CalculateSignal(i);
   }
   
   return rates_total;
}

// REPAINT: Include current bar (bar 0)
int OnCalculate(/* ... */)
{
   // Calculate from bar 0 (current bar)
   int i;
   for(i = limit; i >= 0; i--)  // Include bar 0
   {
      BufferSignal[i] = CalculateSignal(i);
   }
   
   return rates_total;
}

User-Friendly Features

  1. Clear input descriptions: Use descriptive input names
  2. Reasonable defaults: Set sensible default values
  3. Visual customization: Allow color/style customization
  4. Alert options: Provide optional alerts
  5. Multi-timeframe support: Make indicator timeframe-agnostic

Common Pitfalls

Issue 1: Wrong Array Indexing

// ❌ WRONG - Arrays are not time-series by default
BufferMA[rates_total - 1 - i] = value;

// ✅ CORRECT - If not set as series
BufferMA[i] = value;

// ✅ CORRECT - Or explicitly set as series
ArraySetAsSeries(BufferMA, true);
BufferMA[i] = value;  // Now i=0 is newest bar

Issue 2: Not Handling Empty Values

// ✅ CORRECT - Set empty value and use it
SetIndexEmptyValue(0, 0.0);

if(noSignal)
   BufferSignal[i] = 0.0;  // Empty value (gap in line)
else
   BufferSignal[i] = calculatedValue;

Issue 3: Incorrect Buffer Count

// ❌ WRONG - Mismatch between property and actual
#property indicator_buffers 2
// But only using 1 buffer

// ✅ CORRECT - Match declaration
#property indicator_buffers 2
double Buffer1[], Buffer2[];

int OnInit()
{
   IndicatorBuffers(2);
   SetIndexBuffer(0, Buffer1);
   SetIndexBuffer(1, Buffer2);
   return INIT_SUCCEEDED;
}

Summary: MQL4 Indicator Best Practices

  1. Use correct #property directives
  2. Match buffer count between property and SetIndexBuffer calls
  3. Return rates_total from OnCalculate()
  4. Use prev_calculated for efficient recalculation
  5. Set empty values for gaps in plots
  6. Use descriptive labels with SetIndexLabel()
  7. Implement non-repaint logic when appropriate
  8. Provide user customization options
  9. Test on multiple timeframes
  10. Optimize for performance (minimize calculations)

Following these practices ensures professional, efficient, and reliable MQL4 custom indicators.