Skip to main content

Alert Providers

TradeScript chart receiving live market data used for visible price and alert evaluation
The SDK can evaluate simple conditions on the viewed chart stream; a provider owns persistence, off-screen evaluation, and external delivery.

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, or expression conditions.
  • 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:

GoalMethods you implementevaluates
Persist only (SDK keeps evaluating)load, create, update, removeomit
Evaluate yourself (server or off-screen symbols)subscribe (to push triggers), usually the CRUD four tootrue or a list of kinds
Stay in sync across views/devicessubscribe (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 kindDefault evaluatorWith evaluates set
price-above / price-below / price-cross* / percent-changeSDK built-in, against the viewed symbol's streamProvider fires; built-in stands down
drawing-cross / indicator / expressionNobody — needs a providerProvider

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.