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.

Before you start
You need a definition that has been run over history, so there are events to read.
Steps
Open the definition from Tools → Definitions, open its Studies tab and click New study.
Click Show Code and replace the script with the one below. Set
slugandversioninflows.declareto your definition's.Click Save draft.
Click Run Study. The agent proposes a run: pick the datasets, check the Run window, and click Run.
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 |
|---|---|
| Which definition's events the study reads, under which alias, at which version |
| Each result the study publishes: its |
| The current event: |
| Runs first for each event and returns its group from what was known when it fired. With no dimensions declared it returns |
| Measures each event |
| Rows gathered in |
| Sends a finished result, once, from |
Rules that bite early
Studies read events. They never call
ctx.emit, and they have noctx.accum,timeframe(...)handles oroutputs.declare.ctx.collectbelongs inonEventandctx.publishinonFinish. Publishing fromonEventfails the run.Every study exports
scope(ctx), and it returns on every path: a group object, ornull. Lint fails withstudy/scope-requiredwithout the export andstudy/scope-no-returnfor a barereturn;. scope covers what it can read.flows.declareandindicators.declarepin aversion. Lint fails withflows/missing-versionorindicators/missing-versionwithout one.A branch on
ctx.event.flownames a declared alias, or lint fails withstudy/event-flow-branch-mismatch. A read ofctx.event.payload.<key>names a field the definition declares, or lint fails withstudy/payload-key-undeclared.Every result carries a
description: Forward Insight cards show it. Lint warns withstudy/result-missing-descriptionwithout 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 akind: "prose"result withrole: "answer". Lint warns withstudy/readout-missingandstudy/missing-answer-proseotherwise.Never publish a metric whose value is
NaNorInfinity. Guard the empty case, asonFinishdoes 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.
