- 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
16 KiB
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
- Minimize calculations: Only recalculate changed bars
- Use efficient algorithms: Avoid nested loops where possible
- Cache values: Store frequently used calculations
- 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
- Clear input descriptions: Use descriptive input names
- Reasonable defaults: Set sensible default values
- Visual customization: Allow color/style customization
- Alert options: Provide optional alerts
- 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
- ✅ Use correct
#propertydirectives - ✅ Match buffer count between property and SetIndexBuffer calls
- ✅ Return
rates_totalfrom OnCalculate() - ✅ Use
prev_calculatedfor efficient recalculation - ✅ Set empty values for gaps in plots
- ✅ Use descriptive labels with SetIndexLabel()
- ✅ Implement non-repaint logic when appropriate
- ✅ Provide user customization options
- ✅ Test on multiple timeframes
- ✅ Optimize for performance (minimize calculations)
Following these practices ensures professional, efficient, and reliable MQL4 custom indicators.