home Scripting Guide

Lifecycle Hooks & Chart Bars

Every script is built around a small set of lifecycle hooks and the bar data that flows through TD.onChartBar. This page covers both in detail.

Lifecycle Hooks

Add a hook by assigning a function to it - every hook is optional

Hook · When it runs
TD.onStart Runs once, after the chart history has been passed through onChartBar. Use it to subscribe to extra symbols, fetch data, or log a start message.
TD.onStop Runs when you click Stop. Use it to unsubscribe, remove temporary drawings, or save state - it has up to 5 seconds to finish.
TD.onChartBar Runs once for every bar on the chart, then for every live bar. See Working with Chart Bars below.
TD.onReset Runs when the chart's symbol or timeframe changes, just before new history is replayed.
TD.onError Runs when your code throws an error during start or inside a TD.on listener.
Resetting state - TD.onReset = (context) => { ... }

All shapes and plots from your script have already been removed at this point, so clear your own arrays, maps and counters to start fresh. context is the new chart context: { symbol, underlying, exchange, symbolType, interval }. New bars have not arrived yet, so if you need to redraw something that doesn't come from onChartBar, schedule it with setTimeout.

let closes = [];
TD.onReset = (ctx) => {
 closes = [];
 TD.log("Chart changed to " + ctx.symbol);
};

TD.onError = (err) => TD.log("Error: " + err.message);
info

Scrolling the chart back to load older bars does not call onReset. The engine passes newly loaded older bars to onChartBar with isHistory = true, arriving after bars you've already seen - sort by bar.time if order matters.

Working with Chart Bars

TD.onChartBar = (bar, index, isHistory) => { ... }

Argument · Description
bar.time Bar time. History bars use epoch milliseconds (a number).
bar.open/high/low/close Prices
bar.volume 0 or null for symbols without volume, such as cash indices.
index Position in history (0 = oldest). Live bars always use -1.
isHistory true while existing chart bars replay, false for live bars.
Important behaviour
schedule Live bars are 1-minute bars, whatever timeframe the chart uses. On a 15-minute chart you receive a new 1-minute bar every minute - combine them into chart candles yourself, or recalculate only when a new chart candle starts. To receive 5-minute bars directly, use TD.on("bar5min") (see Live Market Data)
speed Keep it fast and synchronous - it runs for thousands of history bars in a row, so don't await data calls inside it (see the Indicators section for the recommended pattern)
auto_awesome Drawing calls auto-use the current bar's time - TD.Chart.plot() and friends, called inside onChartBar, don't need an explicit time
calendar_today Live bar.time may not be a plain number - convert it before comparing, as below
const toMs = (t) => t instanceof Date ? t.getTime()
 : typeof t === "number" ? (t < 1e12 ? t * 1000 : t)
 : Date.parse(t);
Pattern: keep a rolling series

Hold only as many bars as your logic needs, and compare against the previous bar rather than recalculating from scratch.

const bars = [];

TD.onChartBar = (bar, index, isHistory) => {
 bars.push(bar);
 if (bars.length > 500) bars.shift(); // keep memory bounded

 const n = bars.length;
 if (n < 2) return;
 const prev = bars[n - 2];
 if (bar.close > prev.high) {
 TD.Chart.plotMarker(bar.low, "buy");
 }
};
tips_and_updates

Always pair TD.onChartBar with TD.onReset. Almost every "my indicator breaks after I change the symbol" problem is state left over from the previous symbol.