- 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
630 lines
16 KiB
Markdown
630 lines
16 KiB
Markdown
---
|
|
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
|
|
```mql4
|
|
//+------------------------------------------------------------------+
|
|
//| 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
|
|
```mql4
|
|
#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)
|
|
```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.
|