Alerts

View as MarkdownOpen the editor

An alert runs your published indicator in OpenMarket's cloud and tells you when a condition on one of its outputs, or a signal its file declares, comes true.

You set it from the chart: pick an output or a declared signal, choose a condition, and OpenMarket's alerts engine evaluates it on the chart's market with the overlay's settings. There are two ways in: every drawn output is a value a condition can follow, and alert(name, { when, message }) in the file names a ready-made signal.

Set an alert from the chart

  1. Publish the indicator. An alert needs a published version; on a draft the chart says "Publish the Indicator before adding an alert." (Publishing).
  2. Add it to a chart and open the alert dialog from the indicator's bell in the legend, from the right-click menu, or from Alert on > Indicators in the sidebar's alert menu.
  3. Pick the output or a declared signal, pick the condition, and save. The alert runs the version and the settings the overlay carries.

The conditions on an output:

text
Crossing   Crosses above, Crosses below, Greater than, Less than
Channel    Entering channel, Exiting channel, Inside channel, Outside channel
Moving     Moving up, Moving down, Moving up %, Moving down %

A data-only output (declared none) is not offered as a condition target; give it a declared signal instead, as below.

Declare a signal in the file

param("fast", 21, { min: 1, max: 200, description: "Fast EMA length" });
param("slow", 55, { min: 2, max: 400, description: "Slow EMA length" });
output("fast", line, overlay, { color: "#2563eb", description: "Fast EMA of the close" });
output("slow", line, overlay, { color: "#f97316", description: "Slow EMA of the close" });
// Data-only: 1 on the bar the fast EMA crosses above the slow one, else 0.
const goldenCross = output("golden_cross", none);
// A ready-made signal in the chart's alert dialog: it fires when the gate turns from 0 to 1.
alert("golden_cross_up", { when: goldenCross, message: "{{symbol}} golden cross at {{close}}", description: "Fast EMA crosses above slow EMA" });

let fast = new Ema(21);
let slow = new Ema(55);
let cross = new Cross();

function onStart(): void {
  fast = new Ema(i32(p_fast()));
  slow = new Ema(i32(p_slow()));
  cross = new Cross();
}

function onBar(): void {
  const close = bar.close();
  const fastValue = fast.update(close);
  const slowValue = slow.update(close);
  const crossed = cross.update(fastValue, slowValue);
  if (isNaN(slowValue)) return;
  out_fast(fastValue);
  out_slow(slowValue);
  out_golden_cross(crossed == 1 ? 1.0 : 0.0);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

alert(name, { when, message?, description?, text?, every_bar?, title? }) declares a signal over an output handle bound by a top-level const. It fires on the bar the when output turns from false to true (true is finite and nonzero; a NaN row is false and re-arms it), message is the text the alert sends (placeholders such as {{symbol}} and {{close}} fill in when it fires, and {{<output>}} reads a declared output's value on the fired bar), and description is the line under the signal in the alert dialog. Both are at most 200 characters. The when output may be data-only, as here: the file writes golden_cross from onBar() like any other output, and the declaration adds nothing to the compiled module.

Declared signals appear in the chart's alert dialog beside the conditions, and an indicator that declares only signals offers only those. The grammar is on Declarations and the sheet.

Alert words and every bar

An alert can send words your file writes on the bar it fires, and it can fire on every bar its condition holds instead of only the first. Both are options on alert(...).

// Two alerts on an EMA pair: one writes its own message on the cross, one fires on every bar the fast line stays above.
param.int("fast", 21, { min: 1, max: 200 });
param.int("slow", 55, { min: 2, max: 400 });
output("fast", line, overlay, { color: "#2563eb" });
output("slow", line, overlay, { color: "#f97316" });
// Data-only gates: 1 on the bar the condition is true, else 0.
const crossedUp = output("crossed_up", none);
const above = output("above", none);
// The slot the file writes the cross message into.
const crossWords = string("cross_words", { max_bytes: 96 });

// Fires once, on the bar of the cross, with the words the file wrote on that bar. The dialog lists it as "EMA cross up".
alert("cross_up", { when: crossedUp, text: crossWords, title: "EMA cross up", description: "Fast EMA crosses above slow EMA" });
// Fires on every bar the gate is true. {{fast}} and {{slow}} read those outputs on the fired bar.
alert("still_above", { when: above, message: "{{symbol}}: fast {{fast}} is above slow {{slow}}", every_bar: true, description: "Every bar the fast EMA is above the slow EMA" });

let fast = new Ema(21);
let slow = new Ema(55);
let cross = new Cross();

function onStart(): void {
  fast = new Ema(i32(p_fast()));
  slow = new Ema(i32(p_slow()));
  cross = new Cross();
}

function onBar(): void {
  const close = bar.close();
  const fastValue = fast.update(close);
  const slowValue = slow.update(close);
  const crossed = cross.update(fastValue, slowValue);
  if (isNaN(slowValue)) return;
  out_fast(fastValue);
  out_slow(slowValue);
  out_crossed_up(crossed == 1 ? 1.0 : 0.0);
  out_above(fastValue > slowValue ? 1.0 : 0.0);
  // Write the words on the bar the alert fires. On a bar that writes nothing, the alert sends its message, else its name.
  if (crossed == 1) {
    sb_clear();
    sb_text("Fast EMA ");
    sb_auto(fastValue);
    sb_text(" crossed above slow EMA ");
    sb_auto(slowValue);
    str_cross_words_sb();
  }
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine
  • cross_up sends the words the file wrote into cross_words on the bar of the cross, such as "Fast EMA 103.71 crossed above slow EMA 103.7".
  • still_above fires on every bar the fast line is above the slow one, at most once per bar. Its message fills {{fast}} and {{slow}} with those outputs' values on the fired bar.

Options

OptionWhat it doesDefault
whenthe output that turns the alert on: true is finite and not 0required
texta string slot; its words on the fired bar are the messagenone
messagefixed words with placeholders: {{symbol}}, {{exchange}}, {{interval}}, {{open}}, {{high}}, {{low}}, {{close}}, {{volume}}, {{value}}, {{time}}, {{alert.name}}, and {{<output>}} for any output you declaredthe alert's name
every_bartrue fires on every bar when is true, once per bar at most; false fires only on the bar it turns truefalse
descriptionthe line under the signal in the alert dialognone
titlethe words the alert dialog, an armed alert and its notification show for the signal: any characters, spaces included, 1 to 120 of them on one line (the name itself takes letters, digits, ., _ and - only); a newline, a control character or an invisible one (a bidi control, a zero-width space) is refused, as in a text setting's defaultthe alert's name

Which words are sent

  1. The message the user types in the alert dialog, when there is one. Its placeholders fill in as always.
  2. Otherwise the text slot's words on the fired bar. A bar that wrote nothing there, or an empty line, falls through.
  3. Otherwise message. Each {{<output>}} becomes that output's value on the fired bar: a whole number as it is, any other number to 10 significant digits, n/a when the bar has no value. {{close}} and the other placeholders above keep their own meaning, even when an output has the same name.
  4. Otherwise the alert's name.

Words written by the file arrive as plain text. They are one line of at most 200 characters (longer words end with an ellipsis), a link in them is never clickable and never previewed, and mentions such as @everyone and @here are removed. They are never read as a template, so a {{close}} the file writes arrives exactly as written.

Every bar, and the user's choice

The alert dialog shows the file's choice under Fire: "When it turns true" or "Every bar while true". The user can change it for one alert there ("The script's author set the default; your choice applies to this alert."). The repeat and the tick-or-close choices still apply, so a tick alert can fire on the forming bar before it closes.

Limits and refusals

RuleWhat the build says
text names a string slotalert 'cross_up' option 'text' references output 'above' where a string slot is needed
text names something declaredalert 'cross_up' option 'text' references 'ghost', which is neither a declared output nor a declared string slot; bind the output first (const h = output("...", ...)) or the slot (const s = string("...", { max_bytes: 32 })) and pass that const, or name it as a string literal
every_bar is true or falseoption 'every_bar' takes true or false
message and description are at most 200 charactersmessage must be at most 200 characters
title is 1 to 120 charactersalert 'cross_up' title is 121 characters; it takes at most 120, alert 'cross_up' title is empty; it names the alert in the dialog (leave it out to show the name)
title is one line of plain textalert 'cross_up' title carries a newline, a control or an invisible character; shown text is stripped of bidi controls, zero-width characters and control characters, so a title cannot carry them
64 alerts per indicatoralerts must declare at most 64 entries

Strategy alerts

An indicator that trades adds four choices of its own to the alert dialog: "Strategy order placed", "Strategy trade opened or closed", "Strategy position" and "Strategy equity" (Strategies overview).

What an alert can evaluate

An alert evaluates the indicator on the chart's market at the chart's interval, which must be 1m, 5m, 15m, 30m, 1h, 4h or 1d ("Alerts support 1m, 5m, 15m, 30m, 1h, 4h and 1d intervals. Switch the chart interval to arm this indicator."). It serves these inputs:

The indicator readsAn alert serves
Feedsohlcv, trades, oi, liquidations, funding and time, plus book and volume_profile cells
Another marketits candles, through a secondary ohlcv input pinned with symbol and exchange: a crypto venue, a stock or ETF on POLYGON, forex, gold or silver on FX_OTC
A coarser timeframea secondary ohlcv, funding or oi input pinned to 1m, 5m, 15m, 30m, 1h, 4h, 1d or 1w, a whole multiple of the chart's interval; a pinned candle counts only once it has closed
A view of a pinconfirmed (the default), forming and is_new_period

An indicator it cannot evaluate is refused by name when you save the alert:

  • "This Indicator is pinned to a different interval than the chart." for a pin on the first input, a pin finer than the chart's interval or not a whole multiple of it, a custom timeframe such as 2h or 45m, and a forming view or 1w candle too long for the 600 bars an alert keeps (below).
  • "This Indicator reads a data source alerts cannot evaluate yet." for every feed the table does not list, odds, implied_volatility and skew included, and for trade_volume_by_size cells.
  • "Alerts cannot use the Timeframe setting yet. Set it back to its default to arm." for a source, timeframe or symbol setting moved off its default; the sentence names the setting.
  • "This Indicator reads a market alerts cannot evaluate yet (an index)." for an input pinned to an index (POLYGON_INDICES); pin an ETF on the same market instead (SPY/USD).
  • "Indicator alerts are not allowed on this venue." for an indicator whose input pins a CME market.

On CME markets only price alerts are available for now ("Only price alerts are available on CME markets right now."), and where wrun indicator alerts are switched off the chart says "Indicator alerts are not enabled yet."

intrabar cells, candles streams, options_chain cells and an offset view are refused as well.

How much history an alert reads

An alert evaluates over at most 600 bars of the chart's interval. The window is the larger of the warm-up the file declares and the largest maximum among its settings, counted up to 500. A warm-up is one top-level line, warmup({ terms: [{ param: length, times: 2 }], min: 40 }): with length a setting's handle, it asks for twice that setting's value in bars, and at least 40.

An indicator that reads a coarser pinned timeframe gets all 600 bars, even when it reads only that pin's is_new_period pulse; one that also reads book or volume_profile cells keeps the window its warm-up and settings give it. 600 chart bars hold 600 / (leg / chart) candles of a pin: a 4h pin holds 150 candles on a 1h chart, 37 on 15m and 12 on 5m. A higher-timeframe average longer than that arms but stays empty, so arm such alerts on a coarser chart: an average of 20 4h candles fills on 15m and stays empty on 5m.

A forming view needs its whole candle inside the 600 bars, so a 1d one needs a 5m chart or coarser and a 1w one 30m or coarser, and a 1w pin's closed candle needs 1h or coarser; on a finer chart the alert is refused with "This Indicator is pinned to a different interval than the chart."

An indicator that builds a higher timeframe from the chart's own bars, with a Resampler and no pin, must declare warmup() long enough for it: 20 1h candles on a 5m chart are 20 x 12 = 240 bars. Otherwise its alert can arm and stay empty.

Next