--- 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 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) ```mql4 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 ```mql4 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 ```mql4 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 ```mql4 #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 ```mql4 #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 ```mql4 233 - Up arrow 234 - Down arrow 159 - Circle 158 - Square 168 - Triangle up 170 - Triangle down ``` --- ## Multi-Timeframe Indicators ### Getting Data from Other Timeframes ```mql4 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 ```mql4 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 ```mql4 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 ```mql4 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 ```mql4 #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 ```mql4 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 ```mql4 // 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 ```mql4 // ❌ 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 ```mql4 // ✅ 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 ```mql4 // ❌ 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.