MQL4/.github/instructions/mql4-experts.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

22 KiB

applyTo
**/MQL4/Experts/_Thivyam/**/*.mq4

Instructions for _Thivyam MQL4 Expert Advisors

CRITICAL: MQL4 Syntax Compliance

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

Key mandatory rules:

  • Declare loop variables BEFORE for-loop: int i; for(i=0; i<n; i++)
  • Use dot operator (.) for all member access
  • Always use GetLastError() after trade operations
  • Normalize all prices with NormalizeDouble(price, Digits)
  • Normalize lot sizes to broker requirements
  • Use OrderSelect() before accessing order properties

EA Structure and Organization

File Organization

//+------------------------------------------------------------------+
//|                                            YourStrategyName.mq4  |
//|                                            Author: Your Name     |
//|                                     https://www.yourwebsite.com  |
//|                                                    Version: 1.00 |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property link      "https://www.yourwebsite.com"
#property version   "1.00"
#property strict
#property description "Brief description of strategy"
#property description "Additional details or requirements"

// INPUT PARAMETERS
input double InpLotSize = 0.01;
input int    InpStopLossPips = 30;
// ... more inputs

// GLOBAL CONSTANTS
#define MAX_ZONES 100

// GLOBAL VARIABLES
datetime g_lastBarTime = 0;
int g_zoneCount = 0;
// ... more globals

// FUNCTION DECLARATIONS (optional but recommended for organization)

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Initialization code
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Cleanup code
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Main trading logic
}

// HELPER FUNCTIONS
// ... all your helper functions

Initialization (OnInit())

Required Initialization Steps

int OnInit()
{
   // 1. Validate input parameters
   if(InpLotSize <= 0)
   {
      Print("ERROR: LotSize must be greater than 0");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   double minLot = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
   if(InpLotSize < minLot || InpLotSize > maxLot)
   {
      Print("ERROR: LotSize must be between ", minLot, " and ", maxLot);
      return INIT_PARAMETERS_INCORRECT;
   }
   
   // 2. Check trading permissions
   if(!IsTradeAllowed())
   {
      Print("ERROR: Trading is not allowed. Enable AutoTrading.");
      return INIT_FAILED;
   }
   
   // 3. Validate unique magic number
   if(InpMagicNumber == 0)
   {
      Print("WARNING: Magic number is 0. Consider using a unique value.");
   }
   
   // 4. Initialize arrays and data structures
   InitializeArrays();
   
   // 5. Initialize bar time tracker
   g_lastBarTime = iTime(Symbol(), PERIOD_CURRENT, 0);
   
   // 6. Create visual elements (optional)
   CreateInfoPanel();
   
   // 7. Log initialization success
   Print("========================================");
   Print("EA Initialized Successfully");
   Print("Symbol: ", Symbol());
   Print("Magic Number: ", InpMagicNumber);
   Print("Lot Size: ", DoubleToString(InpLotSize, 2));
   Print("========================================");
   
   return INIT_SUCCEEDED;
}

Return Codes

  • INIT_SUCCEEDED - Initialization successful, EA will run
  • INIT_FAILED - Initialization failed, EA will not run
  • INIT_PARAMETERS_INCORRECT - Invalid input parameters

Deinitialization (OnDeinit())

Required Cleanup Steps

void OnDeinit(const int reason)
{
   // 1. Log deinitialization reason
   Print("========================================");
   Print("EA Deinitialization - Reason: ", reason);
   Print("========================================");
   
   // 2. Delete all chart objects created by this EA
   DeleteAllChartObjects();
   
   // 3. Clear chart comment
   Comment("");
   
   // 4. Optional: Close all open positions (usually commented out)
   // CloseAllOrders();
}

Deinitialization Reasons

// Common reason codes:
// 0 - EA removed from chart
// 1 - Program recompiled
// 2 - Symbol or timeframe changed
// 3 - Chart closed
// 5 - Input parameters changed

Main Trading Logic (OnTick())

Structure: Once-Per-Bar Execution

datetime g_lastBarTime = 0;

void OnTick()
{
   // 1. Check for new bar
   datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, 0);
   bool newBar = (currentBarTime != g_lastBarTime);
   
   if(newBar)
   {
      g_lastBarTime = currentBarTime;
      
      // 2. Execute once-per-bar logic
      CheckForEntrySignals();
      UpdateZones();
      ValidateState();
   }
   
   // 3. Execute every-tick logic
   MonitorOpenPositions();
   MonitorPendingOrders();
   
   // 4. Update display
   UpdateInfoPanel();
}

Multi-Timeframe Execution

datetime g_lastH4BarTime = 0;
datetime g_lastM30BarTime = 0;
datetime g_lastM15BarTime = 0;

void OnTick()
{
   // Check for new bars on each timeframe
   bool newH4Bar = IsNewBar(PERIOD_H4);
   bool newM30Bar = IsNewBar(PERIOD_M30);
   bool newM15Bar = IsNewBar(PERIOD_M15);
   
   // Process each timeframe
   if(newH4Bar) ProcessH4();
   if(newM30Bar) ProcessM30();
   if(newM15Bar) ProcessM15();
   
   // Continuous monitoring
   MonitorAllZones();
   UpdateDisplay();
}

bool IsNewBar(int period)
{
   datetime currentBarTime = iTime(Symbol(), period, 0);
   datetime lastBarTime = 0;
   
   if(period == PERIOD_H4) lastBarTime = g_lastH4BarTime;
   else if(period == PERIOD_M30) lastBarTime = g_lastM30BarTime;
   else if(period == PERIOD_M15) lastBarTime = g_lastM15BarTime;
   
   if(currentBarTime != lastBarTime)
   {
      if(period == PERIOD_H4) g_lastH4BarTime = currentBarTime;
      else if(period == PERIOD_M30) g_lastM30BarTime = currentBarTime;
      else if(period == PERIOD_M15) g_lastM15BarTime = currentBarTime;
      return true;
   }
   
   return false;
}

Trade Execution Best Practices

Order Placement Template

int PlacePendingOrder(string type, double entry, double sl, double tp, string comment)
{
   // 1. Determine order type
   int orderType;
   color arrowColor;
   
   if(type == "BuyLimit")
   {
      orderType = OP_BUYLIMIT;
      arrowColor = clrGreen;
   }
   else if(type == "SellLimit")
   {
      orderType = OP_SELLLIMIT;
      arrowColor = clrRed;
   }
   // ... other types
   
   // 2. Validate and normalize prices
   entry = NormalizeDouble(entry, Digits);
   sl = NormalizeDouble(sl, Digits);
   tp = NormalizeDouble(tp, Digits);
   
   if(!IsPriceValid(entry) || !IsPriceValid(sl) || !IsPriceValid(tp))
   {
      Print("ERROR: Invalid price levels");
      return -1;
   }
   
   // 3. Check stop level requirements
   double minStopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
   double slDistance = MathAbs(entry - sl);
   
   if(slDistance < minStopLevel)
   {
      Print("ERROR: SL too close to entry. Min: ", minStopLevel, " Current: ", slDistance);
      return -1;
   }
   
   // 4. Normalize lot size
   double lots = NormalizeLots(InpLotSize);
   
   // 5. Check margin
   double requiredMargin = MarketInfo(Symbol(), MODE_MARGINREQUIRED) * lots;
   double freeMargin = AccountFreeMargin();
   
   if(freeMargin < requiredMargin)
   {
      Print("ERROR: Insufficient margin. Required: ", requiredMargin, " Available: ", freeMargin);
      return -1;
   }
   
   // 6. Place the order
   int ticket = OrderSend(Symbol(), orderType, lots, entry, InpSlippage,
                          sl, tp, comment, InpMagicNumber, 0, arrowColor);
   
   // 7. Handle result
   if(ticket > 0)
   {
      Print("Order placed successfully. Ticket: ", ticket, " Type: ", type,
            " Entry: ", entry, " SL: ", sl, " TP: ", tp);
      return ticket;
   }
   else
   {
      int error = GetLastError();
      Print("OrderSend failed. Error: ", error, " - ", ErrorDescription(error));
      return -1;
   }
}

Position Monitoring Template

void MonitorOpenPositions()
{
   int total = OrdersTotal();
   int i;
   
   for(i = total - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      
      // Filter by symbol and magic number
      if(OrderSymbol() != Symbol()) continue;
      if(OrderMagicNumber() != InpMagicNumber) continue;
      
      // Only process market positions (not pending orders)
      if(OrderType() != OP_BUY && OrderType() != OP_SELL) continue;
      
      // Get position details
      int ticket = OrderTicket();
      double openPrice = OrderOpenPrice();
      double currentSL = OrderStopLoss();
      double currentTP = OrderTakeProfit();
      double profit = OrderProfit();
      
      // Implement trailing stop, break-even, etc.
      if(OrderType() == OP_BUY)
      {
         double newSL = CalculateTrailingStop(OP_BUY, openPrice, currentSL);
         if(newSL > currentSL && newSL < Bid - MarketInfo(Symbol(), MODE_STOPLEVEL) * Point)
         {
            bool modified = OrderModify(ticket, openPrice, newSL, currentTP, 0, clrBlue);
            if(!modified)
            {
               Print("OrderModify failed: ", GetLastError());
            }
         }
      }
      else if(OrderType() == OP_SELL)
      {
         double newSL = CalculateTrailingStop(OP_SELL, openPrice, currentSL);
         if((newSL < currentSL || currentSL == 0) && newSL > Ask + MarketInfo(Symbol(), MODE_STOPLEVEL) * Point)
         {
            bool modified = OrderModify(ticket, openPrice, newSL, currentTP, 0, clrRed);
            if(!modified)
            {
               Print("OrderModify failed: ", GetLastError());
            }
         }
      }
   }
}

Risk Management (MANDATORY)

Dynamic Lot Sizing

// NEVER use hardcoded lot size!
// ALWAYS calculate based on risk percentage and stop loss distance

double CalculateLotSize(double riskPercent, double slPips)
{
   // Get account equity
   double equity = AccountEquity();
   
   // Calculate risk amount in account currency
   double riskAmount = equity * (riskPercent / 100.0);
   
   // Get tick value and size
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
   double point = MarketInfo(Symbol(), MODE_POINT);
   
   // Calculate pip value (handle 4/5 digit brokers)
   double pipValue = tickValue;
   if(Digits == 3 || Digits == 5)
   {
      pipValue = tickValue * 10;
   }
   
   // Calculate lot size
   double lots = 0;
   if(slPips > 0 && pipValue > 0)
   {
      lots = riskAmount / (slPips * pipValue);
   }
   
   // Normalize to broker requirements
   return NormalizeLots(lots);
}

double NormalizeLots(double lots)
{
   double minLot = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   
   // Round to lot step
   lots = MathFloor(lots / lotStep) * lotStep;
   
   // Enforce limits
   lots = MathMax(lots, minLot);
   lots = MathMin(lots, maxLot);
   
   return NormalizeDouble(lots, 2);
}

Usage Example

// User input for risk
input double InpRiskPercent = 1.0;  // Risk per trade (%)

void PlaceTradeWithRiskManagement()
{
   // Calculate SL distance in pips
   double slPips = 30;  // Or calculate from ATR, zone size, etc.
   
   // Calculate position size
   double lots = CalculateLotSize(InpRiskPercent, slPips);
   
   if(lots <= 0)
   {
      Print("ERROR: Cannot calculate valid lot size");
      return;
   }
   
   // Place order with calculated lot size
   int ticket = PlacePendingOrder("BuyLimit", entry, sl, tp, "Risk: " + DoubleToString(InpRiskPercent, 1) + "%");
}

Pattern Detection Best Practices

Bullish Engulfing Detection

bool IsRegularBuy(int timeframe, int index)
{
   // Pattern: Bearish candle followed by Bullish candle that closes above bearish high
   // Index points to the bullish (engulfing) candle
   // index+1 is the bearish (anchor) candle
   
   double anchor_open = iOpen(Symbol(), timeframe, index+1);
   double anchor_close = iClose(Symbol(), timeframe, index+1);
   double anchor_high = iHigh(Symbol(), timeframe, index+1);
   double anchor_low = iLow(Symbol(), timeframe, index+1);
   
   double current_open = iOpen(Symbol(), timeframe, index);
   double current_close = iClose(Symbol(), timeframe, index);
   double current_high = iHigh(Symbol(), timeframe, index);
   double current_low = iLow(Symbol(), timeframe, index);
   
   // Check if anchor is bearish
   if(anchor_open <= anchor_close) return false;
   
   // Check if current is bullish
   if(current_open >= current_close) return false;
   
   // Check if current closes above anchor high (engulfing)
   if(current_close > anchor_high) return true;
   
   return false;
}

Bearish Engulfing Detection

bool IsRegularSell(int timeframe, int index)
{
   // Pattern: Bullish candle followed by Bearish candle that closes below bullish low
   
   double anchor_open = iOpen(Symbol(), timeframe, index+1);
   double anchor_close = iClose(Symbol(), timeframe, index+1);
   double anchor_high = iHigh(Symbol(), timeframe, index+1);
   double anchor_low = iLow(Symbol(), timeframe, index+1);
   
   double current_open = iOpen(Symbol(), timeframe, index);
   double current_close = iClose(Symbol(), timeframe, index);
   double current_high = iHigh(Symbol(), timeframe, index);
   double current_low = iLow(Symbol(), timeframe, index);
   
   // Check if anchor is bullish
   if(anchor_open >= anchor_close) return false;
   
   // Check if current is bearish
   if(current_open <= current_close) return false;
   
   // Check if current closes below anchor low (engulfing)
   if(current_close < anchor_low) return true;
   
   return false;
}

Zone Management Best Practices

Zone Storage Using Arrays

// Define maximum zones
#define MAX_H4_ZONES 50
#define MAX_M30_ZONES 100
#define MAX_M15_ZONES 200

// H4 Zone arrays
double h4_ZoneHighs[MAX_H4_ZONES];
double h4_ZoneLows[MAX_H4_ZONES];
datetime h4_ZoneTimes[MAX_H4_ZONES];
string h4_ZoneTypes[MAX_H4_ZONES];  // "Buy" or "Sell"
bool h4_ZoneActive[MAX_H4_ZONES];
int h4_ZoneCount = 0;

// Initialize arrays
void InitializeArrays()
{
   int i;
   for(i = 0; i < MAX_H4_ZONES; i++)
   {
      h4_ZoneHighs[i] = 0;
      h4_ZoneLows[i] = 0;
      h4_ZoneTimes[i] = 0;
      h4_ZoneTypes[i] = "";
      h4_ZoneActive[i] = false;
   }
   h4_ZoneCount = 0;
}

// Create zone
void CreateH4Zone(string type, int barIndex)
{
   if(h4_ZoneCount >= MAX_H4_ZONES)
   {
      Print("ERROR: Maximum H4 zones reached");
      return;
   }
   
   double high = iHigh(Symbol(), PERIOD_H4, barIndex);
   double low = iLow(Symbol(), PERIOD_H4, barIndex);
   datetime time = iTime(Symbol(), PERIOD_H4, barIndex);
   
   h4_ZoneHighs[h4_ZoneCount] = high;
   h4_ZoneLows[h4_ZoneCount] = low;
   h4_ZoneTimes[h4_ZoneCount] = time;
   h4_ZoneTypes[h4_ZoneCount] = type;
   h4_ZoneActive[h4_ZoneCount] = true;
   
   // Draw zone on chart
   DrawH4Zone(h4_ZoneCount);
   
   h4_ZoneCount++;
}

Zone Drawing

void DrawH4Zone(int zoneIndex)
{
   string objName = "H4_Zone_" + IntegerToString(zoneIndex);
   string labelName = "H4_Label_" + IntegerToString(zoneIndex);
   
   datetime time1 = h4_ZoneTimes[zoneIndex];
   datetime time2 = time1 + (PERIOD_H4 * 60 * 100);  // Extend forward
   
   color zoneColor = (h4_ZoneTypes[zoneIndex] == "Buy") ? clrGreen : clrRed;
   
   // Delete if exists
   ObjectDelete(0, objName);
   ObjectDelete(0, labelName);
   
   // Create rectangle
   ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, h4_ZoneHighs[zoneIndex], time2, h4_ZoneLows[zoneIndex]);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColor);
   ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, true);
   
   // Create label
   string labelText = "H4 " + h4_ZoneTypes[zoneIndex];
   double labelPrice = (h4_ZoneHighs[zoneIndex] + h4_ZoneLows[zoneIndex]) / 2.0;
   
   ObjectCreate(0, labelName, OBJ_TEXT, 0, time1, labelPrice);
   ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, zoneColor);
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
   ObjectSetString(0, labelName, OBJPROP_FONT, "Arial Bold");
   ObjectSetInteger(0, labelName, OBJPROP_SELECTABLE, false);
}

Error Handling (MANDATORY)

Comprehensive Error Handler

string ErrorDescription(int errorCode)
{
   switch(errorCode)
   {
      case 0:   return "No error";
      case 1:   return "No error, result unknown";
      case 2:   return "Common error";
      case 3:   return "Invalid trade parameters";
      case 4:   return "Trade server busy";
      case 5:   return "Old terminal version";
      case 6:   return "No connection with trade server";
      case 7:   return "Not enough rights";
      case 8:   return "Too frequent requests";
      case 64:  return "Account disabled";
      case 65:  return "Invalid account";
      case 128: return "Trade timeout";
      case 129: return "Invalid price";
      case 130: return "Invalid stops (too close to market)";
      case 131: return "Invalid trade volume";
      case 132: return "Market is closed";
      case 133: return "Trading is disabled";
      case 134: return "Not enough money";
      case 135: return "Price changed (requote)";
      case 136: return "Off quotes";
      case 138: return "Requote";
      case 139: return "Order is locked";
      case 141: return "Too many requests";
      case 145: return "Modification denied (too close to market)";
      case 146: return "Trade context is busy";
      case 147: return "Expirations denied by broker";
      case 148: return "Too many open orders";
      default:  return "Unknown error";
   }
}

Visual Display Best Practices

Info Panel Template

void CreateInfoPanel()
{
   string objName = "EA_InfoPanel";
   
   ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, 20);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 9);
   ObjectSetString(0, objName, OBJPROP_FONT, "Courier New");
}

void UpdateInfoPanel()
{
   string objName = "EA_InfoPanel";
   
   string text = "";
   text += "┌─ EA Name v1.00 ───────────┐\n";
   text += "│ Symbol: " + Symbol() + "\n";
   text += "│ Balance: " + DoubleToString(AccountBalance(), 2) + "\n";
   text += "│ Equity: " + DoubleToString(AccountEquity(), 2) + "\n";
   text += "│ Zones: " + IntegerToString(CountActiveZones()) + "\n";
   text += "│ Orders: " + IntegerToString(CountOpenOrders()) + "\n";
   text += "└───────────────────────────┘";
   
   ObjectSetString(0, objName, OBJPROP_TEXT, text);
}

Testing and Validation

Pre-Deployment Checklist

  • All input parameters validated in OnInit()
  • Risk management implemented (no hardcoded lot sizes)
  • All trade operations wrapped with error handling
  • Once-per-bar execution enforced
  • All prices and lots normalized
  • Stop level requirements checked
  • Margin requirements validated before trading
  • All chart objects cleaned up in OnDeinit()
  • Magic number used to filter orders
  • Strategy Tester passes without errors
  • Demo account testing completed successfully

Common Issues to Check

  1. Error 130 (Invalid Stops): Check MODE_STOPLEVEL compliance
  2. Error 131 (Invalid Volume): Verify lot normalization
  3. Error 134 (Not Enough Money): Check margin before trading
  4. Multiple orders on same bar: Implement new bar detection
  5. Orders not closing: Verify OrderSelect() and OrderClose() logic
  6. Zone objects not displaying: Check object property constants
  7. Memory leaks: Ensure all objects deleted in OnDeinit()

Demo/Trial Version Implementation

Time-Limited Demo

int OnInit()
{
   // Demo expiry check
   datetime expiryDate = D'2025.12.31 23:59:59';
   datetime currentDate = TimeCurrent();
   
   if(currentDate >= expiryDate)
   {
      Alert("DEMO VERSION EXPIRED!");
      Print("ERROR: Demo period has ended. Contact vendor for full version.");
      Comment("Demo Version Expired\nContact vendor for full version");
      return INIT_FAILED;
   }
   
   // Log days remaining
   int daysRemaining = (int)((expiryDate - currentDate) / 86400);
   Print("Demo Period: ", daysRemaining, " days remaining until ", TimeToString(expiryDate, TIME_DATE));
   
   // Rest of initialization...
   return INIT_SUCCEEDED;
}

Summary: MQL4 EA Best Practices

  1. Syntax Compliance: Follow mql4-syntax-critical.instructions.md
  2. Input Validation: Validate all user inputs in OnInit()
  3. Risk Management: Calculate lot size dynamically based on risk %
  4. Error Handling: Use GetLastError() after all trade operations
  5. Execution Control: Execute trading logic once per bar
  6. Normalization: Normalize all prices and lot sizes
  7. Margin Check: Verify sufficient margin before placing orders
  8. Magic Number: Use unique magic number to filter orders
  9. Resource Cleanup: Delete all objects in OnDeinit()
  10. Testing: Test thoroughly on demo before live deployment

Following these practices ensures robust, reliable, and professional MQL4 Expert Advisors.