home Scripting Guide

Reference & Support

Logging, alerts and storage; sandbox limits; six complete example scripts; troubleshooting; and a full quick-reference of every TD function.

Logging, Alerts & Storage

Getting information out of your script, and remembering things between runs

TD.log("Price:", bar.close); // normal line
console.log({ a: 1, b: [1, 2] }); // objects shown as formatted JSON
console.warn("Careful"); // prefixed with ??
console.error("Something broke"); // red error line
console.table(rows); // table (first 50 rows, 20 columns)
Console behaviour
compress Very large objects are shortened - arrays after 50 items, text after 5,000 characters
filter_none Identical consecutive lines are grouped with a counter
speed If a script logs too fast, the console skips older lines and shows how many were suppressed - the script itself is not stopped
Toast alerts and storage

Alert types are success, error, info and warning. Storage persists in this browser.

await TD.UI.showAlert("NIFTY crossed 25,000", "success");

await TD.Storage.setItem("myScript.lastSignal", JSON.stringify({ side: "buy", time: Date.now() }));
const raw = await TD.Storage.getItem("myScript.lastSignal");
const last = raw ? JSON.parse(raw) : null;
Storage rules
data_object Values are stored as strings - use JSON.stringify and JSON.parse for objects
label Prefix your keys (e.g. myScript.) so they don't collide with other scripts
lock Keys used by the app for login sessions are protected - scripts can't read or write them

Limits & Sandbox Rules

Scripts run in a secure sandbox, and these limits keep the platform stable

Limit · Value · If exceeded
Scripts running at once 5 - the run is refused with a message
Start-up time ~1 second - the script is halted with "possible infinite loop"
Unresponsive script ~2.5 seconds without responding - the script is terminated with "Infinite loop detected"
Active timers 10 at once, counting setTimeout and setInterval - the script is halted
Minimum timer delay 100 ms - shorter delays are raised to 100 ms, with a notice shown once
Shapes per chart ~2,000 - the oldest shapes are removed automatically
Stop timeout 5 seconds for onStop - after that the script is ended
block

Disabled in scripts: fetch, XMLHttpRequest, WebSocket, EventSource, Worker, document, window, localStorage, sessionStorage, indexedDB, navigator, location, alert, confirm, prompt, eval, Function, importScripts, requestAnimationFrame, queueMicrotask. To get data use the TD.* functions, to show messages use TD.log and TD.UI.showAlert, and to save data use TD.Storage.

warning_amber

Avoid tight loops. while (true) {} or very long synchronous calculations freeze the script and it gets terminated. For repeating work, use TD.on(...) events or a timer of 100 ms or more.

Complete Examples

Six full scripts covering the most common patterns end to end

1. EMA crossover with buy/sell arrows
const FAST = 9, SLOW = 21;
let emaFast = null, emaSlow = null, prevDiff = null, count = 0;

const ema = (prev, price, len) => prev === null ? price : prev + (2 / (len + 1)) * (price - prev);

TD.onReset = () => { emaFast = emaSlow = prevDiff = null; count = 0; };

TD.onChartBar = (bar, index, isHistory) => {
 emaFast = ema(emaFast, bar.close, FAST);
 emaSlow = ema(emaSlow, bar.close, SLOW);
 count++;
 if (count < SLOW) return;

 TD.Chart.plot("EMA Fast", emaFast, { color: "#22C55E" });
 TD.Chart.plot("EMA Slow", emaSlow, { color: "#EF4444" });

 const diff = emaFast - emaSlow;
 if (prevDiff !== null) {
 if (prevDiff <= 0 && diff > 0) {
 TD.Chart.plotMarker(bar.low, "buy");
 if (!isHistory) TD.UI.showAlert("EMA bullish crossover on " + TD.Chart.getSymbol(), "success");
 } else if (prevDiff >= 0 && diff < 0) {
 TD.Chart.plotMarker(bar.high, "sell");
 if (!isHistory) TD.UI.showAlert("EMA bearish crossover on " + TD.Chart.getSymbol(), "warning");
 }
 }
 prevDiff = diff;
};
info

This example updates the EMA on every bar it receives. Live bars are 1-minute bars (see Working with Chart Bars), so on higher timeframes the live values will differ from the chart candles. For exact results, combine 1-minute bars into chart candles first.

2. Previous-day high and low lines
const QUIET = { savable: false, selectable: false, hoverable: false };
let days = new Map(); // "YYYY-MM-DD" -> { high, low }

TD.onReset = () => { days = new Map(); };

TD.onChartBar = (bar, index, isHistory) => {
 if (!isHistory) return;
 const key = new Date(bar.time).toISOString().slice(0, 10);
 const d = days.get(key) || { high: -Infinity, low: Infinity };
 d.high = Math.max(d.high, bar.high);
 d.low = Math.min(d.low, bar.low);
 days.set(key, d);
 scheduleDraw();
};

let timer = null;
function scheduleDraw() {
 clearTimeout(timer);
 timer = setTimeout(draw, 200);
}

function draw() {
 const keys = [...days.keys()].sort();
 if (keys.length < 2) return;
 const prev = days.get(keys[keys.length - 2]);
 TD.Chart.plotShape("horizontalLine", prev.high, Object.assign({ id: "pdh", color: "#16A34A", lineStyle: "dash" }, QUIET));
 TD.Chart.plotShape("horizontalLine", prev.low, Object.assign({ id: "pdl", color: "#DC2626", lineStyle: "dash" }, QUIET));
 TD.Chart.plotText("pdh_t", "PDH", prev.high, Object.assign({ anchor: "right", offset: 0.08, offsetYPx: -8, textColor: "#16A34A" }, QUIET));
 TD.Chart.plotText("pdl_t", "PDL", prev.low, Object.assign({ anchor: "right", offset: 0.08, offsetYPx: 8, textColor: "#DC2626" }, QUIET));
}
3. Live price alert watchlist
const LEVELS = {
 "RELIANCE": { above: 3050, below: 2950 },
 "HDFCBANK": { above: 1750, below: 1680 }
};
const fired = new Set();

TD.on("tick", (d) => {
 const rule = LEVELS[d.symbol];
 if (!rule || !d.ltp) return;

 if (d.ltp >= rule.above && !fired.has(d.symbol + ":up")) {
 fired.add(d.symbol + ":up");
 TD.UI.showAlert(d.symbol + " above " + rule.above + " (" + d.ltp + ")", "success");
 }
 if (d.ltp <= rule.below && !fired.has(d.symbol + ":down")) {
 fired.add(d.symbol + ":down");
 TD.UI.showAlert(d.symbol + " below " + rule.below + " (" + d.ltp + ")", "warning");
 }
});

TD.onStart = async () => {
 await TD.Feed.addSymbols(Object.keys(LEVELS));
 TD.log("Watching " + Object.keys(LEVELS).join(", "));
};
4. Put-call ratio and max-OI strikes from the option chain
const QUIET = { savable: false, selectable: false, hoverable: false };

async function update() {
 const chain = await TD.OptionChain.getOptionChain();
 if (chain.error) { TD.log("Chain error: " + chain.error); return; }

 const c = chain.columns;
 let callOI = 0, putOI = 0, maxCall = null, maxPut = null;
 for (const r of chain.records) {
 const co = Number(r[c.callOI]) || 0, po = Number(r[c.putOI]) || 0;
 callOI += co; putOI += po;
 if (!maxCall || co > maxCall.oi) maxCall = { strike: Number(r[c.strike]), oi: co };
 if (!maxPut || po > maxPut.oi) maxPut = { strike: Number(r[c.strike]), oi: po };
 }
 const pcr = callOI ? (putOI / callOI).toFixed(2) : "–";

 TD.log(chain.resolved.symbol + " " + chain.resolved.expiry + " · PCR " + pcr +
 " · Max CE OI " + maxCall.strike + " · Max PE OI " + maxPut.strike);

 TD.Chart.plotShape("horizontalLine", maxCall.strike, Object.assign({ id: "maxce", color: "#DC2626", lineStyle: "dot" }, QUIET));
 TD.Chart.plotShape("horizontalLine", maxPut.strike, Object.assign({ id: "maxpe", color: "#16A34A", lineStyle: "dot" }, QUIET));
 TD.Chart.plotText("pcr", "PCR " + pcr, maxCall.strike, Object.assign({ anchor: "right", offset: 0.1, offsetYPx: -10 }, QUIET));
}

TD.onStart = async () => {
 await update();
 setInterval(update, 60000); // refresh every minute
};

TD.onReset = () => setTimeout(update, 1500); // chart symbol changed → reload chain
5. Supertrend-filtered signals from chart indicators
const bars = [];
let timer = null;

TD.onReset = () => { bars.length = 0; };

TD.onChartBar = (bar) => {
 bars.push(bar);
 clearTimeout(timer);
 timer = setTimeout(run, 400);
};

async function run() {
 const st = await TD.Chart.getIndicatorValues("FastSuperTrend", { periods: 10, multiplier: 3 });
 if (st.error) { TD.log(st.error); return; }

 const byTime = new Map(bars.map(b => [b.time, b]));
 const up = st.values["Up"];
 for (let i = 1; i < st.times.length; i++) {
 const nowUp = up[i] !== null, wasUp = up[i - 1] !== null;
 const b = byTime.get(st.times[i]);
 if (!b || nowUp === wasUp) continue;
 TD.Chart.plotMarker(nowUp ? b.low : b.high, nowUp ? "buy" : "sell",
 { id: "st_" + b.time, time: b.time });
 }
}
6. Live feed events: ticks, bars, Greeks, bid/ask and depth

Replace OPTION with a live strike and expiry. Option symbols are written as underlying + expiry (YYMMDD) + strike + CE/PE, so NIFTY26100622600CE is the NIFTY 22600 call expiring 06-10-2026. The option symbol receives greeks and bidAsk (NSE top of book), and the BSE symbol receives bidAskL2 (5-level depth).

const OPTION = "NIFTY26100622600CE";
const NSE_EQ = "RELIANCE";
const BSE_EQ = "RELIANCE_BSE";
const SYMBOLS = [OPTION, NSE_EQ, BSE_EQ];

// Quote events can arrive many times a second, so log each symbol at most once every 5s.
const lastLog = {};
const throttled = (key, fn) => {
    const now = Date.now();
    if (now - (lastLog[key] || 0) < 5000) return;
    lastLog[key] = now;
    fn();
};

TD.onStart = async () => {
    try {
        await TD.Feed.addSymbols(SYMBOLS);
        console.log("Subscribed:", SYMBOLS.join(", "));
    } catch (err) {
        console.error("Subscribe failed:", err.message);
    }
};

TD.onStop = async () => {
    await TD.Feed.removeSymbols(SYMBOLS);
    console.log("Unsubscribed");
};

// Trades (also fires when Greeks or bid/ask change for the symbol)
TD.on("tick", (d) => throttled("tick:" + d.symbol, () =>
    console.log(`${d.symbol} LTP ${d.ltp}  Vol ${d.volume}`)
));

// Completed OHLCV bars
TD.on("bar1min", (d) =>
    console.log(`1m ${d.symbol}  O ${d.open} H ${d.high} L ${d.low} C ${d.close} V ${d.volume}`)
);
TD.on("bar5min", (d) =>
    console.log(`5m ${d.symbol}  O ${d.open} H ${d.high} L ${d.low} C ${d.close} V ${d.volume}`)
);

// Option Greeks (option symbols only)
TD.on("greeks", (d) => throttled("greeks:" + d.symbol, () =>
    console.log(`${d.symbol}  IV ${d.iv}  Delta ${d.delta}  Gamma ${d.gamma}  Theta ${d.theta}  Vega ${d.vega}`)
));

// NSE / MCX top of book
TD.on("bidAsk", (d) => throttled("ba:" + d.symbol, () =>
    console.log(`${d.symbol}  Bid ${d.bid} x ${d.bidQty}  |  Ask ${d.ask} x ${d.askQty}  (spread ${(d.ask - d.bid).toFixed(2)})`)
));

// BSE 5-level depth
TD.on("bidAskL2", (d) => throttled("l2:" + d.symbol, () => {
    console.log(`${d.symbol} depth  (total bid ${d.depth.totalBidQty} / ask ${d.depth.totalAskQty})`);
    console.table(d.depth.bids.map((b, i) => ({
        bidOrders: b.orders, bidQty: b.qty, bid: b.price,
        ask: d.depth.asks[i]?.price, askQty: d.depth.asks[i]?.qty, askOrders: d.depth.asks[i]?.orders
    })));
}));

// Connection alive
TD.on("heartbeat", (d) => console.log("Heartbeat", d.timestamp));

Troubleshooting

Common symptoms, their likely cause, and the fix

Symptom · Likely cause and fix
Nothing is drawn Check the console for errors. Make sure you assigned TD.onChartBar = ... and didn't just declare a function, and that your plotted value is a number, not NaN.
Script shows "Finished" immediately It had nothing left to wait for. Add a TD.on(...) listener, TD.onChartBar, or a timer if it should keep running.
"possible infinite loop" The top-level code or a loop takes too long. Move heavy work into functions and break long calculations into smaller pieces.
"Timer limit exceeded" More than 10 timers are active. Reuse a single timer, and clearTimeout the old one before creating a new one.
Drawings are duplicated or pile up Give shapes a stable id so plotting again updates them instead of adding new ones.
Drawings are on the wrong chart A script draws on the chart active when you clicked Run. Stop it, click the chart you want, and run again.
Old drawings remain after stopping This is intended. Click Clear (the eraser icon) while the script is stopped.
Indicator lines break after changing symbol Reset your arrays and state in TD.onReset.
Live values differ from chart candles Live onChartBar bars are 1-minute bars. Combine them into chart candles first - see Working with Chart Bars.
No greeks, bidAsk or bidAskL2 events Subscribe to the symbol first with TD.Feed.addSymbols. greeks fires for option symbols only, bidAsk for NSE and MCX symbols, and bidAskL2 for BSE symbols.
[API Error: ...] in the console The data request failed. Check symbol spelling, date format (YYYY-MM-DD HH:mm:ss), interval, and your data plan.
fetch is disabled Browser networking isn't available in scripts. Use the TD.* data functions.
Stored object comes back as "[object Object]" Use JSON.stringify before TD.Storage.setItem and JSON.parse after getItem.
Debugging tips
table_rows Log a sample before building logic on it - console.table(res.records.slice(0, 5))
filter_alt Log only on specific conditions or on the last bar, not on every history bar - and throttle logging inside live event listeners
bug_report Wrap async work in try { ... } catch (e) { console.error(e); } - the full stack trace is shown in the console

Function Quick Reference

Every function in the TD object, in one place

Lifecycle
 TD.onStart = async () => {}
 TD.onStop = async () => {}
 TD.onChartBar = (bar, index, isHistory) => {}
 TD.onReset = (ctx) => {}
 TD.onError = (err) => {}

Chart
 TD.Chart.getContext() → { symbol, underlying, exchange, symbolType, interval }
 TD.Chart.getSymbol() → string
 TD.Chart.plot(name, value, { color, overlay, style, time })
 TD.Chart.plotMarker(price,"buy"|"sell", { id, color, shape, time })
 TD.Chart.plotShape(type, price, { id, points, color, lineColor, lineWidth, lineStyle,
 fillColor, fillOpacity, textColor, fontSize, time,
 savable, selectable, hoverable, locked })
 TD.Chart.plotBox(id, { low, high, from, to | anchor, offset, width, heightPx, heightAnchor }, styleOptions)
 TD.Chart.plotText(id, text, price, { time | anchor, offset, offsetXPx, offsetYPx, textColor, fontSize })
 TD.Chart.removeShapes(id | [ids])
 TD.Chart.addBuiltInIndicator(name, options)
 await TD.Chart.getIndicatorValues(name, params, { count, add })

Live data
 await TD.Feed.addSymbols([symbols])
 await TD.Feed.removeSymbols([symbols])
 TD.on("tick" | "bar1min" | "bar5min" | "greeks" | "bidAsk" | "bidAskL2" | "heartbeat", callback)

Historical
 await TD.Historical.getBars({ symbol, from, to, interval })
 await TD.Historical.getLastNBars({ symbol, nbars, interval })
 await TD.Historical.getTicks({ symbol, from, to, bidask })
 await TD.Historical.getLastNTicks({ symbol, nticks, bidask })
 await TD.Historical.getAllBars({ timestamp, segment, interval })
 await TD.Historical.getTopNGainers | getTopNLosers | getTopNVolumeGainers({ segment, topn })

Option chain
 await TD.OptionChain.getOptionChain({ symbol?, expiry?, exchange? })
 await TD.OptionChain.getOptionChainWithGreeks({ symbol?, expiry?, exchange? })
 await TD.OptionChain.getExpiryList({ symbol?, exchange? })

Analytics
 await TD.Analytics.getOIGainers | getOILosers({ series, top })
 await TD.Analytics.getIndexGainers | getIndexLosers({ indexName, segment, top })
 await TD.Analytics.getIndustryGainers | getIndustryLosers({ industry, segment, top })
 await TD.Analytics.getMostActiveByTurnover | getMostActiveByVolume({ indexName, segment, top })

Corporate
 await TD.Corporate.getAllResults({ symbol, nature, period, type })
 await TD.Corporate.getPnL | getBalancesheet | getCashflow({ id, cumulative })

Utilities
 TD.log(...args) console.log / warn / error / table
 await TD.UI.showAlert(message, type)
 await TD.Storage.setItem(key, value) await TD.Storage.getItem(key)
 await TD.getMarketStatus()
tips_and_updates

Keep this page open in a second tab while you write your first script - the editor's own autocomplete (type TD.) mirrors this reference exactly.