ChartnautDocs

First definition guide

This walkthrough builds a small definition by hand: it marks every bar that closes above its open, with a label on each mark, ready to run over history and measure with a study.

In the API a definition is a flow: kind: "flow", flows.declare, imports.use({ flowId }). Both words mean the same thing. To have the agent write a definition for you instead, follow Test your first trading idea.

Definition builder with event markers on chart
The definition builder: the script beside the events it marked.

Steps

  1. Open Tools → Definitions and click New.

  2. Click Build it yourself under the suggestions and give the definition a Name.

  3. Paste the script below into the Code tab and click Save draft.

  4. Open the Chart tab and check the marks. The text on each mark is the emit's label, not the event id.

  5. Run the definition over history to collect its events, then build a study on them.

Problems lists what lint flagged, Errors what stopped the script from running, and Logs what it printed with ctx.log.

Full script

meta({
  name: "Bull bar",
  kind: "flow",
  slug: "bull-bar",
  shortName: "BullBar",
});

events.declare({
  id: "bull_bar",
  intent: "signal",
  payload: {
    range: { type: "number", description: "High minus low of the bar, in price units" },
  },
  paint: "pin",
});

outputs.declare({
  id: "close_line",
  kind: "line",
  name: "Close",
  color: "#64b5f6",
});

export function onBar(ctx) {
  ctx.plot("close_line", ctx.close);

  if (ctx.close <= ctx.open) return;

  const range = ctx.high - ctx.low;
  ctx.emit(
    "bull_bar",
    { range },
    {
      label: `Bull · R=${range.toFixed(2)}`,
      subtitle: `close ${ctx.close.toFixed(2)}`,
      price: ctx.close,
    }
  );
}

What each part does

Piece

Job

meta({ kind: "flow" })

Lets the script emit events. kind: "indicator" paints only, and its emits are refused

slug

The stable name studies and other scripts use to depend on this one. Renaming it breaks every consumer

events.declare

The event's id, a short intent tag, and a payload schema. Every payload field has a type and a plain-English description: study agents read those descriptions

paint

How an emit shows on the chart: "pin" (the default), "highlight_event_bar", "highlight_price", "highlight_event_bar_and_price" or "none"

outputs.declare

A series the definition draws, fed by ctx.plot

ctx.emit(id, payload, opts)

Records one event. opts.label is the text on the pin; subtitle is a smaller line under it; price sets where the pin sits on the chart

timeStart and timeEnd in opts set the event's time range and both default to ctx.time. A point-in-time event needs neither.

Rules that bite early

  • Every ctx.emit needs a non-empty label. Lint warns with events/emit-missing-label, and a run drops the emit.

  • Payload keys _displayLabel and _displaySubtitle are reserved. Pass that text as label and subtitle instead.

  • To fire at most once per day, session or level, declare oncePer: { keys: [...] } on the event, naming payload fields. The runtime keeps one emit per unique combination of those fields over the whole run. Never build your own dedupe set in ctx.accum.

  • paint: "highlight_price" and "highlight_event_bar_and_price" draw at a price. Declare payload.price as a number, or lint warns with events/paint-needs-price. opts.price overrides it on the chart only; a run over history ignores it.

  • paint: "none" hides the events on the chart. Lint warns with events/hidden-paint-explicit.

  • The entry file uses export function onBar(ctx). Helpers in other files use relative imports such as import { isBull } from "./lib/bars.js". See Multi-file packages and imports.

Emit and labels and Events and snapshots cover every option.