ChartnautDocs

First study guide

This walkthrough builds a small study by hand: it reads the events of the Bull bar definition from the first definition guide, counts them, and publishes a count, a table and a one-line answer.

A study answers "what usually happens next?" over events a definition already marked. It never marks new events itself. To have the agent build a study for you instead, follow Test your first trading idea.

Study run results in Research
Study results: metrics and tables published from the definition's events.

Before you start

You need a definition that has been run over history, so there are events to read.

Steps

  1. Open the definition from Tools → Definitions, open its Studies tab and click New study.

  2. Click Show Code and replace the script with the one below. Set slug and version in flows.declare to your definition's.

  3. Click Save draft.

  4. Click Run Study. The agent proposes a run: pick the datasets, check the Run window, and click Run.

  5. Read the results on the run card. Read the results explains each part.

Lints in the code view lists what the study's lint found before you run it.

Full script

meta({ name: "Bull bar count", kind: "study" });

flows.declare({ slug: "bull-bar", as: "bull", version: 1 });

results.declare({
  id: "readout__count",
  kind: "metric",
  size: "xs",
  title: "Event count",
  description: "How many bull-bar events the run read.",
});

results.declare({
  id: "rows",
  kind: "table",
  title: "Events",
  description: "One row per bull-bar event: its time and the bar's high-to-low range.",
});

results.declare({
  id: "answer",
  kind: "prose",
  role: "answer",
  title: "Answer",
  description: "The result in one sentence.",
});

export function scope(ctx) {
  if (ctx.event.flow !== "bull") return null;
  return {};
}

export function onEvent(ctx, scope) {
  ctx.collect("rows", {
    time: ctx.event.time,
    range: ctx.event.payload.range,
  });
}

export function onFinish(ctx) {
  const rows = ctx.collected("rows");
  const avg = rows.length ? rows.reduce((sum, r) => sum + r.range, 0) / rows.length : 0;
  ctx.publish("readout__count", { value: rows.length, label: "Events" });
  ctx.publish("rows", rows);
  ctx.publish("answer", {
    body: `${rows.length} bull bars, with an average range of ${avg.toFixed(2)}.`,
    role: "answer",
  });
}

What each part does

Piece

Job

flows.declare({ slug, as, version })

Which definition's events the study reads, under which alias, at which version

results.declare

Each result the study publishes: its kind, title and a plain-English description

ctx.event

The current event: flow (the alias), id, time, instrument and payload

scope(ctx)

Runs first for each event and returns its group from what was known when it fired. With no dimensions declared it returns {}; null leaves the event out

onEvent(ctx, scope)

Measures each event scope kept. The second argument is the group scope returned

ctx.collect / ctx.collected

Rows gathered in onEvent, read back in onFinish

ctx.publish

Sends a finished result, once, from onFinish

Rules that bite early

  • Studies read events. They never call ctx.emit, and they have no ctx.accum, timeframe(...) handles or outputs.declare.

  • ctx.collect belongs in onEvent and ctx.publish in onFinish. Publishing from onEvent fails the run.

  • Every study exports scope(ctx), and it returns on every path: a group object, or null. Lint fails with study/scope-required without the export and study/scope-no-return for a bare return;. scope covers what it can read.

  • flows.declare and indicators.declare pin a version. Lint fails with flows/missing-version or indicators/missing-version without one.

  • A branch on ctx.event.flow names a declared alias, or lint fails with study/event-flow-branch-mismatch. A read of ctx.event.payload.<key> names a field the definition declares, or lint fails with study/payload-key-undeclared.

  • Every result carries a description: Forward Insight cards show it. Lint warns with study/result-missing-description without one.

  • A study with a table or chart result also publishes a small readout__* metric, and a study with two or more non-prose results publishes a kind: "prose" result with role: "answer". Lint warns with study/readout-missing and study/missing-answer-prose otherwise.

  • Never publish a metric whose value is NaN or Infinity. Guard the empty case, as onFinish does above.

This study only counts, so it has nothing to put on Charts. A Forward Insight needs a study that measures what followed each event, with dimensions.declare for the ways it splits events. Declarations covers dimensions and their roles, and Collect and publish covers every result kind.