Alert Providers

The SDK evaluates simple price-* / percent-change conditions itself. Everything beyond that —
persistence, firing for symbols you are not currently viewing, evaluating expression conditions, and
delivering out of the app — plugs in behind an optional AlertProvider. It is the datafeed-parallel
seam: design your chart against the alert API, then drop in a client-side or server-backed provider
without changing anything on the chart surface.
import { createTradeScriptSdk } from '@tradescript/pro/sdk/core';
const sdk = await createTradeScriptSdk({ lease: deploymentLease });
sdk.chart.mount({
mount: '#chart',
datafeed,
alerts: myAlertProvider, // omit for built-in price evaluation only
});
When you need a provider
You do not need one when price-* / percent-change alerts on the currently viewed symbol are enough —
the built-in evaluator covers that, and alerts round-trip with getState() / setState().
Implement (or compose) a provider when you need any of:
- Persistence beyond chart state — alerts stored in your backend or browser storage, shared across views and devices.
- Firing for symbols the user is not currently viewing.
- Evaluating
drawing-cross,indicator, orexpressionconditions. - Out-of-app delivery — email, SMS, push, webhook.
There is no silent fallback: if alerts must fire while a symbol is off-screen, a provider has to own that evaluation.
The minimum contract
Every AlertProvider method is optional — the "required" set depends on which job the provider does:
| Goal | Methods you implement | evaluates |
|---|---|---|
| Persist only (SDK keeps evaluating) | load, create, update, remove | omit |
| Evaluate yourself (server or off-screen symbols) | subscribe (to push triggers), usually the CRUD four too | true or a list of kinds |
| Stay in sync across views/devices | subscribe (to push out-of-band changes) | as above |
The smallest useful provider is persistence-only:
const provider: AlertProvider = {
load: (filter) => api.listAlerts(filter),
create: (alert) => api.createAlert(alert),
update: (id, patch) => api.updateAlert(id, patch),
remove: (id) => api.deleteAlert(id),
// no `evaluates` → the SDK's built-in evaluator keeps firing price-* conditions
};
Verify it in one pass: call chart.addAlert({ condition: { kind: 'price-above', price: 100 } }) and
confirm your create was hit; reload the page and confirm load returns the alert and its line
reappears on the chart at the same price. If both hold, the contract is wired correctly — everything
else on this page is optional behavior on top.
The evaluation boundary
Who checks each condition kind is a capability negotiation, not a fixed split:
| Condition kind | Default evaluator | With evaluates set |
|---|---|---|
price-above / price-below / price-cross* / percent-change | SDK built-in, against the viewed symbol's stream | Provider fires; built-in stands down |
drawing-cross / indicator / expression | Nobody — needs a provider | Provider |
The evaluates capability is what prevents double-firing: if your provider evaluates price-above
server-side, set evaluates: true (or list the kinds) and the SDK's built-in evaluator stands down.
The AlertProvider interface
Every method is optional. Omit the provider entirely and the SDK still evaluates price-* conditions
against the loaded symbol.
interface AlertProvider {
// Which conditions the provider evaluates itself. `true` = it fires everything (the built-in
// evaluator stands down); an array = only those kinds; omitted = the provider only persists.
evaluates?: boolean | AlertConditionKind[];
load?: (filter?: AlertFilter) => Promise<AlertDefinition[]> | AlertDefinition[];
create?: (alert: AlertDefinition) => Promise<AlertDefinition> | AlertDefinition;
update?: (alertId: string, patch: AlertPatch) => Promise<void> | void;
remove?: (alertId: string) => Promise<void> | void;
// The provider pushes state back in: alerts changed out of band (another device), and — crucially —
// triggers it evaluated itself (server-side, cross-symbol, expressions).
subscribe?: (callback: AlertProviderCallback) => Unsubscribe;
}
Evaluation locus
The built-in evaluator runs against the controller's own realtime stream. That covers the SDK-managed
data path. In the mounted widget — or to fire for symbols the user is not currently viewing, or to
evaluate expression conditions and deliver out of app — configure a provider. There is no silent
fallback: if you need alerts to fire while a symbol is off-screen, wire a provider that owns evaluation.
Ready-made pieces
Persistence stores
Optional store providers mirror the bar-cache battery factories. They persist alerts and emit created/updated/removed so multiple views stay in sync; they do not evaluate conditions.
import {
createInMemoryAlertStore,
createLocalStorageAlertStore,
} from '@tradescript/pro/sdk/advanced';
// Replace a localStorage-only alert provider.
const store = createLocalStorageAlertStore({ key: 'my-app:alerts' });
sdk.chart.mount({ mount: '#chart', datafeed, alerts: store });
Client-side evaluator
createClientAlertEvaluator composes a store with per-symbol quote subscriptions over the datafeed and
the built-in evaluator, so price-* conditions fire for every active-alert symbol — even ones not on
screen. It declares evaluates: true, so the chart's in-chart evaluator stands down and nothing
double-fires.
import { createClientAlertEvaluator, createLocalStorageAlertStore } from '@tradescript/pro/sdk/advanced';
const alerts = createClientAlertEvaluator({
datafeed,
store: createLocalStorageAlertStore(),
interval: '1', // stream used for evaluation; defaults to 1-minute
});
sdk.chart.mount({ mount: '#chart', datafeed, alerts });
It subscribes only to symbols with active alerts and unsubscribes when the last alert on a symbol is removed or disabled.
TradeScript expressions
createTradeScriptAlertEvaluator bridges expression alerts to the TradeScript language. This is where
an expression alert and a strategy's alert.condition(...) resolve through the same path and surface as
the same managed alert. The evaluate step is injected, so the SDK stays decoupled from the language
runtime — pass a function that runs the expression and returns whether it currently holds.
import { createClientAlertEvaluator, createTradeScriptAlertEvaluator } from '@tradescript/pro/sdk/advanced';
const alerts = createClientAlertEvaluator({
datafeed,
evaluators: [
createTradeScriptAlertEvaluator({
evaluate: (expression, { bars }) => runTradeScript(expression, bars), // your compiler/runtime
}),
],
});
const mounted = sdk.chart.mount({ mount: '#chart', datafeed, alerts });
const chart = (await mounted.ready()).chart();
// Now any TradeScript boolean is an alert:
chart.addAlert({ condition: { kind: 'expression', expression: 'rsi(14) > 70 and close > vwap' } });
Server-backed provider
A production provider persists to your backend and evaluates server-side so alerts fire even when the app
is closed (email/SMS/webhook). Because it evaluates, it declares evaluates: true and pushes triggers
through subscribe:
const serverAlerts: AlertProvider = {
evaluates: true,
load: (filter) => api.listAlerts(filter),
create: (alert) => api.createAlert(alert),
update: (id, patch) => api.updateAlert(id, patch),
remove: (id) => api.deleteAlert(id),
subscribe: (cb) => api.streamAlertEvents(cb), // pushes { type: 'triggered', ... } and CRUD echoes
};
sdk.chart.mount({ mount: '#chart', datafeed, alerts: serverAlerts });
The chart renders whatever the provider reports — armed lines, triggered state, and out-of-band changes — with no chart-side code changes when you move from the client evaluator to a server one.
Next steps
- Alerts — the alert object, lifecycle, conditions, and on-chart interaction the provider feeds.
- Backend integration — authentication and tenant patterns shared with persisted chart state.
- Chart Events — the alert events your application subscribes to.