Problem Description
Solution
To create a multi-currency scanner in MQL4, the best approach is to build a Dashboard Indicator. This allows you to attach the tool to a single chart (e.g., EURUSD) while it monitors specific logic (MA Crossover) across many other pairs in the background.
Key Features of this Tool:
- Multi-Symbol Scanning: You can input a comma-separated list of pairs (e.g.,
EURUSD,GBPUSD,USDJPY). - Customizable MA Settings: Change periods, methods (SMA, EMA), and timeframes.
- Alert System: Includes Pop-up alerts, Push notifications (mobile), and Email.
- Visual Dashboard: Displays the status of each pair directly on the chart.
- Spam Prevention: Alerts are triggered only once per candle close to prevent repetitive alerts.
MQL4 Code
Save this code as MA_Cross_Scanner.mq4 in your MQL4/Indicators folder.
//+------------------------------------------------------------------+
//| MA_Cross_Scanner.mq4 |
//| Copyright 2023, Quantitative |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Quantitative"
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
#property indicator_chart_window
//--- INPUT PARAMETERS
input string InpSymbols = "EURUSD,GBPUSD,USDJPY,AUDUSD,USDCAD"; // Comma-separated symbols
input ENUM_TIMEFRAMES InpTF = PERIOD_H1; // Timeframe to scan
input int InpFastPeriod = 12; // Fast MA Period
input int InpSlowPeriod = 26; // Slow MA Period
input ENUM_MA_METHOD InpMethod = MODE_EMA; // MA Method
input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE;// Applied Price
input bool InpAlertPopup = true; // Popup Alert
input bool InpAlertPush = false; // Push Notification
input bool InpAlertEmail = false; // Email Alert
//--- GLOBAL VARIABLES
string g_symbols[];
datetime g_last_alert_time[];
int g_total_symbols = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 1. Split the input string into an array of symbols
string sep = ",";
ushort u_sep = StringGetCharacter(sep, 0);
g_total_symbols = StringSplit(InpSymbols, u_sep, g_symbols);
// 2. Resize the alert tracking array
ArrayResize(g_last_alert_time, g_total_symbols);
ArrayInitialize(g_last_alert_time, 0);
// 3. Create the visual dashboard
for(int i = 0; i < g_total_symbols; i++)
{
// Trim whitespace
StringTrimLeft(g_symbols[i]);
StringTrimRight(g_symbols[i]);
CreateLabel("Lbl_Sym_" + string(i), g_symbols[i], 20, 30 + (i * 20), clrWhite);
CreateLabel("Lbl_Stat_" + string(i), "Scanning...", 100, 30 + (i * 20), clrGray);
}
CreateLabel("Lbl_Header", "MA CROSS SCANNER (" + EnumToString(InpTF) + ")", 20, 10, clrGold);
// 4. Use a Timer to scan symbols not on the current chart
EventSetTimer(1); // Scan every 1 second
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
ObjectsDeleteAll(0, "Lbl_");
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
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[])
{
// We use OnTimer for multi-currency scanning to ensure updates
// even if the current chart doesn't receive ticks.
return(rates_total);
}
//+------------------------------------------------------------------+
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
for(int i = 0; i < g_total_symbols; i++)
{
string sym = g_symbols[i];
// Ensure symbol is available in Market Watch
if(MarketInfo(sym, MODE_BID) <= 0)
{
UpdateLabel("Lbl_Stat_" + string(i), "Invalid Symbol", clrDimGray);
continue;
}
// Get MA values for Candle 1 (Previous closed) and Candle 2
double fast1 = iMA(sym, InpTF, InpFastPeriod, 0, InpMethod, InpPrice, 1);
double slow1 = iMA(sym, InpTF, InpSlowPeriod, 0, InpMethod, InpPrice, 1);
double fast2 = iMA(sym, InpTF, InpFastPeriod, 0, InpMethod, InpPrice, 2);
double slow2 = iMA(sym, InpTF, InpSlowPeriod, 0, InpMethod, InpPrice, 2);
// Get time of the last closed bar
datetime barTime = iTime(sym, InpTF, 1);
// Logic Check
bool isBullish = (fast1 > slow1) && (fast2 <= slow2);
bool isBearish = (fast1 < slow1) && (fast2 >= slow2);
// Update Dashboard & Alert
if(isBullish)
{
UpdateLabel("Lbl_Stat_" + string(i), "BUY CROSS", clrLime);
ProcessAlert(i, sym, "BUY", barTime);
}
else if(isBearish)
{
UpdateLabel("Lbl_Stat_" + string(i), "SELL CROSS", clrRed);
ProcessAlert(i, sym, "SELL", barTime);
}
else
{
// Determine current state without crossover
if(fast1 > slow1) UpdateLabel("Lbl_Stat_" + string(i), "Bullish Trend", clrGreen);
else UpdateLabel("Lbl_Stat_" + string(i), "Bearish Trend", clrMaroon);
}
}
}
//+------------------------------------------------------------------+
//| Helper: Process Alerts |
//+------------------------------------------------------------------+
void ProcessAlert(int index, string symbol, string type, datetime barTime)
{
// Check if we already alerted for this specific bar
if(g_last_alert_time[index] != barTime)
{
string msg = symbol + " (" + EnumToString(InpTF) + ") MA Crossover: " + type;
if(InpAlertPopup) Alert(msg);
if(InpAlertPush) SendNotification(msg);
if(InpAlertEmail) SendMail("MA Scanner Alert", msg);
// Update last alert time to prevent spam
g_last_alert_time[index] = barTime;
}
}
//+------------------------------------------------------------------+
//| Helper: Create Dashboard Label |
//+------------------------------------------------------------------+
void CreateLabel(string name, string text, int x, int y, color clr)
{
if(ObjectFind(0, name) < 0)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
ObjectSetString(0, name, OBJPROP_FONT, "Arial");
}
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
}
//+------------------------------------------------------------------+
//| Helper: Update Dashboard Label |
//+------------------------------------------------------------------+
void UpdateLabel(string name, string text, color clr)
{
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
}
//+------------------------------------------------------------------+
How to Use This Tool
-
Installation:
- Open MetaTrader 4.
- Press
F4to open the MetaEditor. - Right-click on the
Indicatorsfolder in the Navigator and select "New File". - Name it
MA_Cross_Scanner, select "Custom Indicator", and click Finish. - Paste the code above into the file, replacing everything else.
- Press
F7to Compile.
-
Setup:
- Go back to the main MT4 window.
- Open a chart (e.g., EURUSD). It doesn't matter which one, as the tool scans independently.
- Drag the
MA_Cross_Scannerfrom the Navigator onto the chart.
-
Configuration (Inputs Tab):
- InpSymbols: Type the pairs you want to scan, separated by commas (e.g.,
EURUSD,GBPUSD,XAUUSD). Note: These symbols must be visible in your "Market Watch" window, or the scanner won't be able to retrieve data. - InpTF: Select the timeframe to scan (e.g., 1 Hour, 4 Hour).
- MA Settings: Set your Fast and Slow MA periods and method (Simple, Exponential, etc.).
- Alerts: Set
InpAlertPushtotrueif you want notifications on your phone (requires MT4 mobile setup).
- InpSymbols: Type the pairs you want to scan, separated by commas (e.g.,
How the Logic Works
- Closed Bar Confirmation: The scanner looks at index
1(the previously closed candle) and index2(the candle before that). - Crossover Definition:
- Buy: Fast MA was below Slow MA at index 2, but is above Slow MA at index 1.
- Sell: Fast MA was above Slow MA at index 2, but is below Slow MA at index 1.
- Timer: It uses
OnTimer(running every 1 second) instead ofOnTick. This is crucial because if you put the scanner on a quiet pair (like a minor cross),OnTickwouldn't fire often enough to alert you about a move on a busy pair (like Gold).
Q&A
Q: Why does the dashboard say "Invalid Symbol"?
A: The symbol name in the input list must match exactly what is in your Market Watch window. If your broker uses suffixes (e.g., EURUSD.pro), you must type EURUSD.pro in the input list.
Q: Can I scan multiple timeframes at once?
A: This specific code scans one timeframe (selected in inputs) across multiple pairs. To scan multiple timeframes, simply drag the indicator onto the chart a second time and select a different timeframe in the inputs.
Q: Why aren't alerts triggering immediately when lines cross?
A: This tool waits for the candle to close (confirmed cross). This prevents "repainting" where the lines cross during a volatile candle but un-cross before the candle closes, leading to false signals.