Alerts
PineTS supports Pine Script’s alert() and alertcondition() functions, allowing indicators and strategies to emit alert events that your application can capture and act on — for example, sending webhook notifications, triggering trades, or logging signals.
Table of Contents
Overview
| Function | Purpose | Fires when |
|---|---|---|
alert(message, freq) | Imperative — fires when the code path executes it | Code reaches the alert() call |
alertcondition(condition, title, message) | Declarative — fires when a boolean condition is true | condition evaluates to true |
By default, alerts only fire on the last (realtime) bar, matching TradingView’s behavior. Use setAlertMode('all') to fire on every bar for backtesting.
alert()
Fires an alert event when executed. Supports frequency gating to control how often the alert triggers.
Pine Script Syntax
//@version=6
indicator("EMA Cross Alert", overlay=true)
ema9 = ta.ema(close, 9)
ema21 = ta.ema(close, 21)
if ta.crossover(ema9, ema21)
alert("Bullish EMA cross! Price: " + str.tostring(close), alert.freq_once_per_bar)
if ta.crossunder(ema9, ema21)
alert("Bearish EMA cross! Price: " + str.tostring(close), alert.freq_once_per_bar)
plot(ema9, "EMA 9", color.blue)
plot(ema21, "EMA 21", color.red)
Frequency Constants
| Constant | Behavior |
|---|---|
alert.freq_all | Every alert() call triggers (including multiple calls per bar) |
alert.freq_once_per_bar | Only the first alert() call per bar triggers (default) |
alert.freq_once_per_bar_close | Only triggers on the bar’s final tick (when it closes/confirms) |
If no frequency is specified, alert.freq_once_per_bar is used.
alertcondition()
Registers a named alert condition that fires when a boolean expression is true. Unlike alert(), it doesn’t support frequency gating — it fires once per bar when the condition is met.
Pine Script Syntax
//@version=6
indicator("RSI Alert Conditions")
rsi = ta.rsi(close, 14)
alertcondition(rsi > 70, "Overbought", "RSI crossed above 70!")
alertcondition(rsi < 30, "Oversold", "RSI dropped below 30!")
plot(rsi, "RSI")
hline(70, "Overbought")
hline(30, "Oversold")
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
condition | series bool | Yes | When true, the alert fires |
title | const string | No | Name of the alert condition |
message | const string | No | Message included in the alert event |
Capturing Alerts
With run() (Promise-based)
After execution, alerts are available on the returned context object:
import { PineTS, Provider } from 'pinets';
const pine = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 100);
const code = `
//@version=6
indicator("Alert Demo")
if ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
alert("Bullish cross detected!", alert.freq_once_per_bar)
alertcondition(ta.rsi(close, 14) > 70, "Overbought", "RSI > 70")
plot(close)
`;
const ctx = await pine.run(code);
// Access all alert events
for (const a of ctx.alerts) {
console.log(`[${a.type}] ${a.message} (bar ${a.bar_index})`);
}
With stream() (Event-based)
In streaming mode, alerts are emitted as 'alert' events in real time:
import { PineTS, Provider } from 'pinets';
const pine = new PineTS(Provider.Binance, 'BTCUSDT', '1m', 100);
const code = `
//@version=6
indicator("Live Alert")
if ta.crossover(close, ta.sma(close, 20))
alert("Price crossed above SMA 20!", alert.freq_once_per_bar)
plot(close)
`;
const evt = pine.stream(code, { live: true, interval: 5000 });
evt.on('alert', (alert) => {
console.log('ALERT:', alert.message);
// Send to webhook, trigger trade, send notification, etc.
// sendWebhook(alert.message);
});
evt.on('data', (ctx) => {
// Normal data processing
});
evt.on('error', (err) => {
console.error('Error:', err);
});
Alert Event Structure
Each alert event (whether from alert() or alertcondition()) has this shape:
{
type: 'alert' | 'alertcondition',
message: string, // The alert message
title?: string, // Title (alertcondition only)
freq?: string, // Frequency constant (alert only)
bar_index: number, // Bar index where the alert fired
time: number // Bar open time (ms timestamp)
}
Alert Modes
By default, PineTS matches TradingView behavior: alerts only fire on the last (realtime) bar. Historical bars are silently skipped. This is the correct behavior for live trading.
For backtesting, you can switch to 'all' mode to fire alerts on every bar:
const pine = new PineTS(Provider.Binance, 'BTCUSDT', 'D', 365);
// Backtest mode — fire alerts on ALL bars
pine.setAlertMode('all');
const code = `
//@version=6
indicator("Backtest Alerts")
if ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
alert("Buy signal", alert.freq_once_per_bar)
if ta.crossunder(ta.ema(close, 9), ta.ema(close, 21))
alert("Sell signal", alert.freq_once_per_bar)
plot(close)
`;
const ctx = await pine.run(code);
console.log(`Total alerts over ${ctx.marketData.length} bars: ${ctx.alerts.length}`);
for (const a of ctx.alerts) {
const date = new Date(a.time).toISOString().slice(0, 10);
console.log(` ${date}: ${a.message}`);
}
| Mode | Behavior | Use case |
|---|---|---|
'realtime' (default) | Alerts fire only on the last bar | Live trading, webhooks |
'all' | Alerts fire on every bar | Backtesting, signal analysis |
Complete Examples
Example 1: Webhook-Ready Live Alerts
import { PineTS, Provider } from 'pinets';
const pine = new PineTS(Provider.Binance, 'BTCUSDT', '5', 200);
const strategy = `
//@version=6
indicator("Scalp Alerts", overlay=true)
fast = ta.ema(close, 8)
slow = ta.ema(close, 21)
rsi = ta.rsi(close, 14)
// Multi-condition alert
if ta.crossover(fast, slow) and rsi > 50
alert("LONG entry: EMA cross + RSI > 50 at " + str.tostring(close), alert.freq_once_per_bar)
if ta.crossunder(fast, slow) and rsi < 50
alert("SHORT entry: EMA cross + RSI < 50 at " + str.tostring(close), alert.freq_once_per_bar)
plot(fast, "Fast EMA", color.green)
plot(slow, "Slow EMA", color.red)
`;
const evt = pine.stream(strategy, { live: true, interval: 10000 });
evt.on('alert', async (alert) => {
console.log(`[${new Date().toISOString()}] ${alert.message}`);
// await fetch('https://your-webhook.com/alerts', {
// method: 'POST',
// body: JSON.stringify(alert)
// });
});
evt.on('data', (ctx) => {
const close = ctx.marketData[ctx.idx]?.close;
console.log(`Bar ${ctx.idx}: close=${close}`);
});
Example 2: Backtest Signal Analysis
import { PineTS, Provider } from 'pinets';
async function backtestAlerts() {
const pine = new PineTS(Provider.Binance, 'BTCUSDT', 'D', undefined, new Date('2024-01-01').getTime(), new Date('2024-12-31').getTime());
pine.setAlertMode('all');
const code = `
//@version=6
indicator("Backtest Signals")
rsi = ta.rsi(close, 14)
alertcondition(rsi < 30, "Oversold", "RSI oversold — potential buy")
alertcondition(rsi > 70, "Overbought", "RSI overbought — potential sell")
plot(rsi)
`;
const ctx = await pine.run(code);
const buys = ctx.alerts.filter((a) => a.title === 'Oversold');
const sells = ctx.alerts.filter((a) => a.title === 'Overbought');
console.log(`Oversold signals: ${buys.length}`);
console.log(`Overbought signals: ${sells.length}`);
for (const signal of ctx.alerts) {
const date = new Date(signal.time).toISOString().slice(0, 10);
console.log(` ${date}: [${signal.title}] ${signal.message}`);
}
}
backtestAlerts();
Example 3: Custom Data with Alerts
import { PineTS } from 'pinets';
const data = [
{ openTime: Date.now() - 86400000 * 5, open: 100, high: 110, low: 95, close: 105, volume: 1000, closeTime: Date.now() - 86400000 * 4 },
{ openTime: Date.now() - 86400000 * 4, open: 105, high: 115, low: 100, close: 112, volume: 1200, closeTime: Date.now() - 86400000 * 3 },
{ openTime: Date.now() - 86400000 * 3, open: 112, high: 120, low: 108, close: 98, volume: 800, closeTime: Date.now() - 86400000 * 2 },
{ openTime: Date.now() - 86400000 * 2, open: 98, high: 105, low: 90, close: 92, volume: 1500, closeTime: Date.now() - 86400000 * 1 },
{ openTime: Date.now() - 86400000 * 1, open: 92, high: 100, low: 88, close: 95, volume: 900, closeTime: Date.now() },
];
const pine = new PineTS(data);
pine.setAlertMode('all');
const code = `
//@version=6
indicator("Custom Alerts")
if close < open
alert("Bearish bar at " + str.tostring(close))
plot(close)
`;
const ctx = await pine.run(code);
console.log('Alerts:', ctx.alerts.length);