--- 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 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 ```mql4 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 ```mql4 // 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 ```mql4 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 ```mql4 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 ```mql4 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 ```mql4 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 ```mql4 // 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 ```mql4 // 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 ```mql4 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 ```mql4 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 ```mql4 // 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 ```mql4 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 ```mql4 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 ```mql4 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 ```mql4 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.