Alerts: Telegram & Voice
Fire alerts the moment a signal triggers - on-chart AlertIf, a Telegram message, and a spoken voice alert.
- ·AlertIf basics
- ·Suppressing repeats
- ·Telegram bot alerts
- ·InternetOpenURL
- ·Voice (Say) alerts
- ·Once-per-bar logic
A backtest is a story about the past. A live chart is the present knocking on your door - and the whole point of building a system is to be told, instantly, when it fires. You should not have to stare at a screen all day. In this chapter your formula learns to raise its hand: write a line to AmiBroker's alert log, speak the signal out loud, and push a message straight to a Telegram channel on your phone.
AlertIf can email you, run a program, play a sound, or speak out loud. The Say() function uses your computer's built-in text-to-speech, so AmiBroker can literally announce a signal as it happens.
The hard part of alerting is not sending one message - it is not sending a hundred. A chart refreshes constantly, and a naive alert will re-fire on every tick until you want to throw the laptop out of the window. So alongside each method we will build the discipline that makes alerts usable: fire once per completed bar, and never again for the same signal.
AlertIf: the built-in alert log
The gentlest way in is AlertIf. It writes to AmiBroker's own Alert Output window and can also e-mail or play a sound, all without any web plumbing.
Buy = Cross(EMA(Close, 10), EMA(Close, 20));
Sell = Cross(EMA(Close, 20), EMA(Close, 10));
// type 1 = log to the Alert Output window
AlertIf(Buy, "", "BUY signal at " + NumToStr(Close, 1.2), 1);
AlertIf(Sell, "", "SELL signal at " + NumToStr(Close, 1.2), 2);
The third argument is the message, and the last number is just an ID so AmiBroker can tell your alerts apart. AlertIf already does some de-duplication of its own across bars, which makes it a friendly first step. But for Telegram and voice we send the request ourselves, so we must handle repeats by hand - which brings us to the single most important idea in this chapter.
Once per bar: the suppression pattern
A live formula re-runs many times inside the same bar as new ticks arrive. If Buy is true, it stays true for the whole bar, so a plain if (Buy) Say(...) would speak on every refresh. We want exactly one alert when the last, still-forming bar first turns the signal on, and then silence until something changes.
The author's teaching pattern uses static variables - values that survive between refreshes - to remember whether we have already alerted for this bar. Two pieces do the work: a per-chart name so two charts never clash, and the current bar's index as a fingerprint.
// a unique tag for THIS symbol + chart + interval
static_name_ = Name() + GetChartID() + Interval(2);
// a text fingerprint of the most recent bar
lastBar = StrFormat("%0.f", LastValue(BarIndex()));
We check LastValue(Buy) - the signal on the latest bar only - and fire just once, recording the bar we fired on. If the same signal is still true on the next refresh of the same bar, the stored fingerprint matches and we stay quiet.
The golden rule of alerting: act on LastValue(signal) - the most recent bar - and remember the bar you alerted on. Comparing the stored bar fingerprint to the current one is what turns an endless stream of duplicate messages into a single, trustworthy "ping" per signal.
Spoken alerts with Say
Say reads text aloud through your computer's voice. It is wonderful for hands-free monitoring - you can watch one chart and hear another. Wrapped in the suppression pattern, it speaks each signal exactly once:
RequestTimedRefresh(1, False); // nudge the formula to re-run each second
static_name_ = Name() + GetChartID() + Interval(2);
lastBar = StrFormat("%0.f", LastValue(BarIndex()));
// --- BUY voice alert, once per bar ---
if (LastValue(Buy) AND StaticVarGet(static_name_ + "BuyAlert") == 0
AND StaticVarGetText(static_name_ + "BuyAlert_bar") != lastBar)
{
Say("Buy signal triggered");
StaticVarSetText(static_name_ + "BuyAlert_bar", lastBar);
StaticVarSet(static_name_ + "BuyAlert", 1);
}
else if (!LastValue(Buy))
{
StaticVarSet(static_name_ + "BuyAlert", 0); // reset when the signal clears
}
RequestTimedRefresh(1, False) asks AmiBroker to re-evaluate the formula about once a second even if no tick arrived, so the alert is timely. The else if resets the flag once Buy goes back to false, arming the system cleanly for the next genuine signal.
Telegram: a message to your phone
To reach your phone, we send an HTTP request to Telegram's bot API. You need two things first: a bot (created through Telegram's BotFather, which hands you a token) and a channel the bot can post to. AmiBroker fires the request with InternetOpenURL and then closes the handle with InternetClose.
// PLACEHOLDERS - paste your own from BotFather; never share a real token
TelegramToken = "1234567890:REPLACE_WITH_YOUR_BOT_TOKEN";
TelegramChat = "@your_channel_name";
Message = Name() + " | " + Interval(2) + " | BUY at " + NumToStr(LastValue(Close), 1.2);
url = "https://api.telegram.org/bot" + TelegramToken +
"/sendMessage?chat_id=" + TelegramChat + "&text=" + Message;
ih = InternetOpenURL(url); // sends the request
if (ih) InternetClose(ih); // always close the handle you opened
The whole trick is building that URL string: the token, the channel and the text glued together with +. Drop it inside the same suppression block as the voice alert and you get one tidy Telegram message per signal instead of a barrage.
Your bot token is a password to your bot - anyone holding it can post as you. Keep it in placeholders like the ones above when you share or publish code, and never commit a real token to a file you push anywhere. The values shown here are deliberately fake.
Putting it together
In practice you wire every channel into one formula, each guarded by the same once-per-bar check, and switch them on or off independently with ParamList. Here is the complete, copy-pasteable all-in-one alert block - built on the author's Telegram Alerts with Auto Suppression.afl - covering a spoken alert, an optional on-screen pop-up, and a Telegram push, for both the Buy and the Sell side:
_SECTION_BEGIN("EMA Crossover with All-in-One Alerts");
RequestTimedRefresh(1, False); // re-run about once a second so alerts are timely
SetChartOptions(0, chartShowArrows | chartShowDates);
SetTradeDelays(0, 0, 0, 0);
ema10 = EMA(Close, 10);
ema20 = EMA(Close, 20);
Plot(Close, "Close", colorDefault, GetPriceStyle());
Plot(ema10, "EMA10", colorBlue, styleLine);
Plot(ema20, "EMA20", colorRed, styleLine);
// clean, non-repainting signals (Chapter 22)
Buy = Ref(Cross(ema10, ema20), -1);
Sell = Ref(Cross(ema20, ema10), -1);
Buy = ExRem(Buy, Sell);
Sell = ExRem(Sell, Buy);
// --- alert channels, each independently switchable ---
VoiceAlert = ParamList("Voice Alert", "Disable|Enable", 1);
PopupAlert = ParamList("Popup Alert", "Disable|Enable", 0);
TelegramAlert = ParamList("Telegram Alert", "Disable|Enable", 1);
TelegramAPI_ID = ParamStr("Telegram Bot API Key", "1234567890:REPLACE_WITH_YOUR_BOT_TOKEN");
TelegramChat_ID = ParamStr("Telegram Channel ID", "@your_channel_name");
// --- per-chart suppression keys ---
static_name_ = Name() + GetChartID() + Interval(2);
lastBar = StrFormat("%0.f", LastValue(BarIndex()));
// ===== BUY alert: fire once, on the last bar =====
if (LastValue(Buy) AND StaticVarGet(static_name_ + "BuyAlert") == 0
AND StaticVarGetText(static_name_ + "BuyAlert_bar") != lastBar)
{
msg = Name() + " | " + Interval(2) + " | BUY at " + NumToStr(LastValue(Close), 1.2);
if (VoiceAlert == "Enable") Say("Buy signal triggered");
if (PopupAlert == "Enable") PopupWindow(msg, "Buy Signal");
if (TelegramAlert == "Enable")
{
url = "https://api.telegram.org/bot" + TelegramAPI_ID +
"/sendMessage?chat_id=" + TelegramChat_ID + "&text=" + msg;
ih = InternetOpenURL(url);
if (ih) InternetClose(ih);
}
StaticVarSetText(static_name_ + "BuyAlert_bar", lastBar);
StaticVarSet(static_name_ + "BuyAlert", 1);
}
else if (!LastValue(Buy))
StaticVarSet(static_name_ + "BuyAlert", 0); // re-arm when Buy clears
// ===== SELL alert: the exact mirror of the BUY block =====
if (LastValue(Sell) AND StaticVarGet(static_name_ + "SellAlert") == 0
AND StaticVarGetText(static_name_ + "SellAlert_bar") != lastBar)
{
msg = Name() + " | " + Interval(2) + " | SELL at " + NumToStr(LastValue(Close), 1.2);
if (VoiceAlert == "Enable") Say("Sell signal triggered");
if (PopupAlert == "Enable") PopupWindow(msg, "Sell Signal");
if (TelegramAlert == "Enable")
{
url = "https://api.telegram.org/bot" + TelegramAPI_ID +
"/sendMessage?chat_id=" + TelegramChat_ID + "&text=" + msg;
ih = InternetOpenURL(url);
if (ih) InternetClose(ih);
}
StaticVarSetText(static_name_ + "SellAlert_bar", lastBar);
StaticVarSet(static_name_ + "SellAlert", 1);
}
else if (!LastValue(Sell))
StaticVarSet(static_name_ + "SellAlert", 0);
_SECTION_END();
The two alert blocks are exact mirrors - the Sell side is the Buy side with every Buy swapped for Sell. Each channel lives inside the guarded block, so all of them fire exactly once per signal: Say speaks it, PopupWindow(text, title) flashes an on-screen box, and the Telegram request pushes it to your phone. Flip any of the three ParamList switches to silence a channel without touching the code - voice at your desk, Telegram when you step away, a pop-up when you want a visual nudge.
One compatibility note: the internet and static-variable functions used here have evolved across AmiBroker versions. The author's own alert templates open with Version(6.17); (or similar) to declare the minimum build the code expects. If a function behaves oddly, check your build under Help > About and update - older versions may lack the newer InternetOpenURL or StaticVarGetText behaviour.
Try it yourself
- Add
AlertIfto your favourite system and open Window > Alert Output to watch the log fill as signals fire. - Apply the voice block to a 5-minute chart and listen for a single spoken alert per crossover - not one per tick.
- Create a Telegram bot through BotFather, drop the token and channel into the placeholders, and send yourself a test "BUY" message.
- Deliberately remove the static-variable check for one refresh and watch the duplicate spam - then put it back and appreciate the difference.
Recap
AlertIfwrites to AmiBroker's built-in alert log and is the easiest place to start.- The once-per-bar suppression pattern -
LastValue(signal)plus a stored bar fingerprint in static variables - is what stops endless duplicate alerts. Sayspeaks a signal aloud andPopupWindowflashes an on-screen box;RequestTimedRefreshkeeps the formula re-running so alerts are timely.- Telegram alerts are just an
InternetOpenURLrequest to the bot API, always closed withInternetClose; keep your token in placeholders. - Mind the
Version()of AmiBroker the code expects, and toggle each channel withParamList.
Next we take the final step - turning that same signal into a real order through OpenAlgo, with sandbox safety first.