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.
//@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.
//+------------------------------------------------------------------+
//| 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
alertcondition(crossOb, "RSI overbought",
"RSI crossed above the overbought level on {{ticker}} ({{interval}}), close {{close}}")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.
alert(msg, alert.freq_once_per_bar_close)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.
alert.freq_once_per_barint bar = rates_total - 1; // the forming barJudge the forming bar, first time true only. The RSI can cross back before the close, as in Pine.
// TradingView: pop-up, app, e-mail, webhookAlert(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.
hline(ob, "Overbought", color.gray)IndicatorSetInteger(INDICATOR_LEVELS, 2);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpOb);Levels that come from inputs are set in OnInit() rather than with #property.
// alerts never fire on historical barsif(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.
input.string(options = [...]) // a choice of frequencyenum 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
- 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.
- 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.
- 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.