millionchart
فارسی

Lesson 11: Alerts that say something

Turn a signal into an alert with a message that carries the symbol and the price, and learn how often an alert may ring.

Lesson 11 of 18. An arrow on the chart is only seen when you look; an alert is meant to reach you wherever you are. A script has two ways to declare one: alert.when, which names a condition for the alert dialog, and alert.send(), which rings with a message the script builds.

A condition with a message#

Cuprel
indicator "Cross alerts"
    edition = 1
    display = price

let fast = tech.sma(close, 10)
let slow = tech.sma(close, 30)
alert.when(
    tech.crosses_above(fast, slow),
    "Fast above slow",
    message = "{{ticker}}: fast crossed above slow at {{close}}",
)
alert.when(
    tech.crosses_below(fast, slow),
    "Fast below slow",
    message = "{{ticker}}: fast crossed below slow at {{close}}",
)
plot(fast, name = "Fast", color = color.blue)
plot(slow, name = "Slow", color = color.red)

alert.when(condition, name, message) declares a condition the alert dialog can use, under its name. It rings on the bar where the condition turns true, not on every bar where it stays true. If you want every bar, add on = "level".

The message is plain text. The server fills the words in double braces when an alert rings (an alert on your own script does not ring in this release, see the warning above):

PlaceholderFilled with
{{ticker}}the symbol
{{exchange}}the feed it comes from
{{interval}}the chart timeframe
{{open}} {{high}} {{low}} {{close}} {{volume}}the bar that rang, prices with at most the symbol's own number of decimals and no trailing zeros (4318.5)
{{price}}the price at the moment it rang
{{time}} {{clock.now}}the bar's time and the time it rang, in UTC
{{alert_name}}the name you gave the alert
{{script_message}}the text the script's own alert.send() call gave
{{plot("Fast")}}the value of the plot whose name is "Fast", on that bar

A placeholder the server does not know stays as written.

A message the script builds#

When the message must carry a value the script computed, use alert.send() with a formatted text. f"..." puts expressions in braces; after a colon you say how to write the number.

Cuprel
indicator "Breakout alert"
    edition = 1
    display = price

input length: int = 20, name = "Lookback", min = 2
let top = tech.highest(high, length)[1]
if close > top
    alert.send(
        f"{symbol.ticker}: closed above the {length}-bar high {top:price}",
        key = "breakout",
        once = "bar_close",
    )
plot(top, name = "High", color = color.orange)
  • top:price writes the number with the symbol's own decimals: 4318.50 for gold, 1.08215 for a currency pair.
  • tech.highest(high, length)[1] is the highest high of the last length bars, read one bar back, so the current bar is compared with bars that came before it.
  • key names this alert call. Two calls with different keys are two alerts, even on the same bar. Give every call a key you can recognise.
  • once = "bar_close" rings only on a bar that has closed. The default, "bar", may ring while the bar is still forming, once per bar.

How often it may ring#

once says when a key may ring again, and cooldown adds a wait after it rang.

onceRings
"bar"at most once for each bar, closed or still forming (the default)
"bar_close"at most once for each bar, and only after the bar has closed
"per_day"at most once for each trading day
"on_change"when the message differs from the last one this key sent
"all"on every call
Cuprel
indicator "Cooled-down alert"
    edition = 1
    display = price

let move = math.abs(close - close[1])
if move > tech.atr(14) * 2
    alert.send(f"Big move of {move:.2f}", key = "move", once = "bar_close", cooldown = 12)
plot(close, name = "Close")

tech.atr(14) is the average true range, the usual size of a bar's move over the last 14 bars. cooldown = 12 means: this key may ring on one bar and not again before 12 bars have passed.

Do not put the placeholders in an f-text#

{{ticker}} belongs to a plain text that the server fills. Inside f"..." the double brace means "the character itself", so f"{{ticker}}" is the text {ticker}. The checker warns when it sees this (see The editor for what the editor lists).

Cuprel · warning W-FTEXT-PLACEHOLDER
indicator "Wrong placeholder"
    edition = 1
    display = price

if close > open
    alert.send(f"{{ticker}} closed up at {close:price}", key = "up")
plot(close)

Write the placeholder in a plain text, and the value you computed in an f-text. Join them with +, or send two alerts:

Cuprel
indicator "Right placeholder"
    edition = 1
    display = price

if close > open
    alert.send("{{ticker}} closed up at " + text.of(close, tick), key = "up", once = "bar_close")
plot(close)

Try changing#

  • In "Cross alerts", add a mark on the same condition, mark.shape(tech.crosses_above(fast, slow), name = "Would ring", shape = triangle_up, place = below, color = color.green), so you can see on the chart where the alert would ring. Write the crossing on a line of its own first (let up = ...) and use up in both calls.
  • Add {{interval}} and {{plot("Fast")}} to the message of alert.when; the editor accepts any placeholder in a plain text.
  • In "Breakout alert", change once = "bar_close" to once = "per_day" and save: the editor shows no problem.
  • Make the key a setting, input k: text = "up", name = "Key" and key = k, and read the problem the editor reports: E5102, a key must be written in the script.

Checkpoint#

  • You can declare a named condition with alert.when and build a message with alert.send().
  • You can choose how often a key may ring with once and cooldown.
  • You know why a placeholder such as {{ticker}} belongs in a plain text, not in an f-text.
  • You know that alerts on your own scripts do not reach you in this release.

See also: Alert keys and options, Text and number formatting, Reading the editor's messages.

Next: Lesson 12: Modules you reuse