Edgoro
Pine Script v6 → MQL5 custom indicator

alertcondition() and alert() in MQL5: pop-up, push and e-mail

Short answer

MetaTrader 5 has no alertcondition(): there's no dialog where you pick an indicator's condition. The program raises the alert itself with Alert() (pop-up and sound), SendNotification() (push to the MetaTrader mobile app) or SendMail(), once per bar, judged on the bar that just closed for alert.freq_once_per_bar_close.

Pinealertconditionalertalert.freq_once_per_bar_closealert.freq_once_per_barhline

MQL5AlertSendNotificationSendMailStringFormatiRSI

  • MQL5: compiled in MetaEditor, 0 errors, 0 warnings
  • Pine: checked by Edgoro Pine Check, not run in TradingView

The Pine Script v6 code

An RSI indicator with overbought and oversold levels, two alertcondition() alerts to pick in TradingView's dialog, and an alert() call with a message built at run time.

Checked by Edgoro Pine Check with no errors and no warnings. Pine Check is a static checker, not TradingView's compiler, and this script was not run in TradingView.

Pine Script v6
//@version=6
indicator("RSI alerts", overlay = false)

len = input.int(14, "RSI length", minval = 1)
ob = input.float(70, "Overbought")
os = input.float(30, "Oversold")

rsi = ta.rsi(close, len)
crossOb = ta.crossover(rsi, ob)
crossOs = ta.crossunder(rsi, os)

plot(rsi, "RSI", color.purple, 2)
hline(ob, "Overbought", color.gray)
hline(os, "Oversold", color.gray)

// 1. alertcondition(): a choice in TradingView's "Create alert" dialog (constant message, {{placeholders}})
alertcondition(crossOb, "RSI overbought", "RSI crossed above the overbought level on {{ticker}} ({{interval}}), close {{close}}")
alertcondition(crossOs, "RSI oversold", "RSI crossed below the oversold level on {{ticker}} ({{interval}}), close {{close}}")

// 2. alert(): fires from the code (one "Any alert() function call" alert), with a message built at run time
if crossOb or crossOs
    alert("RSI(" + str.tostring(len) + ") at " + str.tostring(rsi, "#.0") + " on " + syminfo.ticker, alert.freq_once_per_bar_close)

The MQL5 program

A custom indicator with the same RSI and levels that sends its own alerts: pop-up, push notification and e-mail, each switched on in the inputs. It never trades.

Save it as MQL5/Indicators/alertcondition-to-alerts.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
//+------------------------------------------------------------------+
//| alertcondition() and alert() in MQL5: RSI alerts coded in the    |
//| indicator (pop-up, push notification, e-mail).                   |
//| Custom indicator: save in MQL5/Indicators and compile (F7).      |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_buffers 1
#property indicator_plots   1

#property indicator_label1  "RSI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrMediumPurple
#property indicator_width1  2

enum ENUM_ALERT_FREQ
  {
   FREQ_BAR_CLOSE,   // Once per bar close (alert.freq_once_per_bar_close)
   FREQ_ONCE_PER_BAR // Once per bar, as soon as it happens (alert.freq_once_per_bar)
  };

input int             InpLen   = 14;             // RSI length
input double          InpOb    = 70;             // Overbought
input double          InpOs    = 30;             // Oversold
input ENUM_ALERT_FREQ InpFreq  = FREQ_BAR_CLOSE; // When to alert
input bool            InpPopup = true;           // Pop-up alert
input bool            InpPush  = false;          // Push notification to the MetaTrader app
input bool            InpMail  = false;          // E-mail (set up in Tools > Options > Email)

double   RsiBuf[];
int      rsiHandle    = INVALID_HANDLE;
datetime lastAlertBar = 0;

int OnInit()
  {
   if(InpLen < 1 || InpOs >= InpOb)
      return INIT_PARAMETERS_INCORRECT;
   SetIndexBuffer(0, RsiBuf, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   // hline(ob) / hline(os): levels of the indicator window
   IndicatorSetInteger(INDICATOR_LEVELS, 2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpOb);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, InpOs);
   IndicatorSetInteger(INDICATOR_DIGITS, 1);
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("RSI alerts (%d)", InpLen));

   rsiHandle = iRSI(_Symbol, _Period, InpLen, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
      return INIT_FAILED;
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(rsiHandle);
  }

// Sends the message by every channel switched on in the inputs
void Notify(const string msg)
  {
   Print(msg);
   if(InpPopup)
      Alert(msg);
   if(InpPush && !SendNotification(msg))
      Print("Push notification failed, error ", GetLastError());
   if(InpMail && !SendMail("RSI alert " + _Symbol, msg))
      Print("E-mail failed, error ", GetLastError());
  }

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[])
  {
   if(rates_total < 3 || BarsCalculated(rsiHandle) < rates_total)
      return prev_calculated;

   int start = prev_calculated > 0 ? prev_calculated - 1 : 0;
   int count = rates_total - start;
   double rsi[];
   if(CopyBuffer(rsiHandle, 0, 0, count, rsi) != count)
      return prev_calculated;
   for(int k = 0; k < count; k++)
      RsiBuf[start + k] = rsi[k];

   // Pine alerts only fire in real time: skip the history, and the bar that closed before we started
   if(prev_calculated == 0)
     {
      lastAlertBar = time[rates_total - 2];
      return rates_total;
     }

   // Bar close: judge the bar that just closed. Once per bar: judge the forming bar, first time only
   int bar = InpFreq == FREQ_BAR_CLOSE ? rates_total - 2 : rates_total - 1;
   if(time[bar] == lastAlertBar)
      return rates_total;

   string what = "";
   if(RsiBuf[bar] > InpOb && RsiBuf[bar - 1] <= InpOb)       // ta.crossover(rsi, ob)
      what = "crossed above the overbought level";
   else if(RsiBuf[bar] < InpOs && RsiBuf[bar - 1] >= InpOs)  // ta.crossunder(rsi, os)
      what = "crossed below the oversold level";
   if(what == "")
      return rates_total;

   lastAlertBar = time[bar];
   // {{ticker}}, {{interval}} and {{close}} become _Symbol, the timeframe and close[bar]
   Notify(StringFormat("RSI(%d) %s on %s %s: RSI %.1f, close %s", InpLen, what, _Symbol,
                       StringSubstr(EnumToString((ENUM_TIMEFRAMES)_Period), 7), RsiBuf[bar], DoubleToString(close[bar], _Digits)));
   return rates_total;
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
alertcondition(crossOb, "RSI overbought",
  "RSI crossed above the overbought level on {{ticker}} ({{interval}}), close {{close}}")
MQL5
Notify(StringFormat("RSI(%d) %s on %s %s: RSI %.1f, close %s",
       InpLen, what, _Symbol,
       StringSubstr(EnumToString((ENUM_TIMEFRAMES)_Period), 7),
       RsiBuf[bar], DoubleToString(close[bar], _Digits)));

No list of conditions and no placeholders: the message is built with StringFormat() from _Symbol, the timeframe's name (PERIOD_H1 without its prefix) and the bar's values.

Pine Script v6
alert(msg, alert.freq_once_per_bar_close)
MQL5
int bar = rates_total - 2;  // the bar that just closed
if(time[bar] == lastAlertBar)
   return rates_total;
...
lastAlertBar = time[bar];

Judge the closed bar and remember its open time, so the following ticks of the new bar don't repeat the alert.

Pine Script v6
alert.freq_once_per_bar
MQL5
int bar = rates_total - 1;  // the forming bar

Judge the forming bar, first time true only. The RSI can cross back before the close, as in Pine.

Pine Script v6
// TradingView: pop-up, app, e-mail, webhook
MQL5
Alert(msg);
SendNotification(msg);
SendMail("RSI alert " + _Symbol, msg);

Push notifications need your MetaQuotes ID in Tools > Options > Notifications; e-mail needs a mail server in Tools > Options > Email.

Pine Script v6
hline(ob, "Overbought", color.gray)
MQL5
IndicatorSetInteger(INDICATOR_LEVELS, 2);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpOb);

Levels that come from inputs are set in OnInit() rather than with #property.

Pine Script v6
// alerts never fire on historical bars
MQL5
if(prev_calculated == 0)
  {
   lastAlertBar = time[rates_total - 2];
   return rates_total;
  }

The first call calculates the history without alerting, and marks the last closed bar as already handled.

Pine Script v6
input.string(options = [...])  // a choice of frequency
MQL5
enum ENUM_ALERT_FREQ
  {
   FREQ_BAR_CLOSE,   // Once per bar close
   FREQ_ONCE_PER_BAR // Once per bar, as soon as it happens
  };

A dropdown input is an enum; the comments become the labels.

Pitfalls

  • The terminal must be running. TradingView evaluates alerts on its servers; an MQL5 alert exists only while the terminal runs with the chart open. For alerts around the clock, run MetaTrader 5 on a VPS.
  • Push limits. The MQL5 Reference limits SendNotification() to 2 calls per second and 10 per minute, and it doesn't work in the Strategy Tester. Keep to one message per signal.
  • Webhooks need an Expert Advisor. WebRequest() can't be called from an indicator; to post to a URL, move the logic into an EA, as in sending a webhook with WebRequest.
  • Intrabar alerts. On the forming bar, a value that goes back and forth across the level would alert again and again without the stored bar time; in bar-close mode, a cross that undoes itself before the close never alerts.
  • One chart, one symbol. The indicator alerts for the chart it is on. For several symbols, attach it to several charts, or write an EA that loops over symbols.

Checking that both give the same values

  1. On a 1-minute chart, set the levels close to the current RSI (for example 55 and 45) on both platforms to get alerts quickly.
  2. In TradingView, create one alert on "RSI overbought" and one on "Any alert() function call"; in MetaTrader 5, attach the indicator with pop-ups on.
  3. In bar-close mode both should alert after the same bar closes. The RSI values in the messages agree when the closes agree.

These programs show one technique each; they are not trading systems, and none of this is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

The same topic in each language

All MQL5 errors

Rather not port it by hand?

Edgoro can generate the MQL5 version for you from a plain-English description of the rules, and compiles it in MetaEditor before you get it. Test it on a demo account: a clean build isn't proof that a strategy works.