Examples
Custom API levels
Price levels from your own API, polled every 30 seconds.

Shows: ChartOverlay. ctx.data.poll through our proxy, a hand-written DataSchema that checks the JSON, DataEvent handled with an exhaustive switch, and ctx.streams.levels.latest in draw.
See also: Streams and history.
import { defineScript, input, type DataSchema } from "@tradingthings/tt-create";
/** What the API returns: `[{ "price": 5012.25, "label": "Call wall" }, …]`. */
interface Level {
readonly price: number;
readonly label: string;
}
/** Checks the JSON. A zod schema (`z.array(...)`) fits here as well. */
const LEVELS: DataSchema<readonly Level[]> = {
safeParse: (json) => {
if (!Array.isArray(json)) return { success: false, error: "expected an array" };
const levels: Level[] = [];
for (const item of json) {
if (typeof item?.price !== "number") return { success: false, error: `no price in ${JSON.stringify(item)}` };
levels.push({ price: item.price, label: typeof item.label === "string" ? item.label : "" });
}
return { success: true, data: levels };
},
};
/**
* Price levels from your own API, polled every 30 s, as lines with labels.
* A failed request keeps the last levels and shows the error in the legend.
*/
export default defineScript({
name: "API levels",
surface: { kind: "ChartOverlay" },
inputs: {
url: input.string({ default: "https://example.com/levels.json", label: "URL" }),
line: input.lineStyle({ default: { color: "#42a5f5", width: 1, dash: [6, 4] }, label: "Line" }),
},
subscribe: (ctx) => ({
levels: ctx.data.poll({ url: ctx.inputs.url, every: "30s", schema: LEVELS }),
}),
state: () => ({ error: null as string | null }),
on: {
levels: (s, event, ctx) => {
switch (event.type) {
case "Value":
s.error = null;
return;
case "Error":
s.error = event.error.type;
ctx.log("levels failed", event.error);
return;
default:
event satisfies never;
}
},
},
draw: (_s, frame, g, ctx) => {
// The last valid value stays in the view, also after an error.
const levels = ctx.streams.levels.latest ?? [];
const { line } = ctx.inputs;
for (const level of levels) {
const y = frame.priceToY(level.price);
if (y < 0 || y > frame.height) continue;
g.line(0, y, frame.width, y, line);
g.text(level.label, 6, y - 2, { color: line.color, baseline: "bottom" });
g.priceLabel(level.price, frame.formatPrice(level.price), line.color, "#ffffff");
}
},
legend: (s, _crosshairTime, ctx) => [
{ label: "Levels", value: String(ctx.streams.levels.latest?.length ?? 0) },
...(s.error ? [{ label: "Error", value: s.error, color: "#ef5350" }] : []),
],
});