FAQ
Answers to the questions people ask most about scripting in Chartnaut. Each answer is complete here; links go to the full reference.
Getting started
Do I need to write code?
No. The Agent and the builders write and fix scripts for you, and most people never open the Code tab. Code is for when you want exact control, want to review what the Agent changed, or want to read an error yourself.
Indicator, definition or study: which do I want?
An indicator draws values on the chart: lines, bands, zones. A definition marks moments in the market as events, for example every break of the first 15-minute range of the London session on EURUSD, and can be run over history. A study reads a definition's events and measures what happened after them. Studies feed Forward Insights on the chart.
Can I use npm packages?
No. Scripts run in a sandbox with no npm and no network. A definition can split its code across files and import them with relative paths that include the extension, such as import { levels } from "./levels.js". Indicators and studies are single files. fetch, async, await, timers and Promises are refused by lint.
Why can't I use Date.now() or new Date()?
A script runs over bars, not over wall time, and its results are cached. The same bars must always give the same answer. Read the bar's time from ctx.time, or the event's time in a study. Math.random is seeded in studies; use ctx.rand.seeded(n) there when you need randomness.
Where do I see ctx.log output?
In the builder's Logs tab. Outside the builders ctx.log does nothing, so it is safe to leave in.
History and warmup
What is warmup, and how much do I need?
Warmup is how far back a script's state remembers. Declare it with a top-level warmup(...) call after your inputs, picking the term that matches the state:
Your state | Declare |
|---|---|
A lookback of N bars |
|
An EMA |
|
RSI, ATR, ADX (Wilder smoothing) |
|
Resets every session or day |
|
Resets weekly or monthly |
|
Carries the previous session forward |
|
Never resets: a running total, count or all-time high |
|
The declaration is the largest need across all the state in the script: w.max(w.day(), w.wilder(length)). A script that keeps no state declares nothing. Every term is on Warmup and memory.
Why is window/warmup-required blocking my save?
The script keeps state (ctx.accum or a handle's state) and declares no warmup. Without one, its values depend on where the chart's data happens to start, so two charts show different numbers. Add a warmup(...) call after your inputs, using the table above.
What is the difference between warmup and retain?
warmup(...) is how far back the script walks so its state settles. retain(...) is how many past buckets stay readable through handle.bars(n). The highest of the last 250 daily highs needs retain({ "1d": 250 }) and no warmup. Both count buckets of the timeframe being read, not chart bars.
Why does my indicator say "History capped"?
Your plan limits how many buckets a script can hold per timeframe: 2,500 on Free, 5,000 on Starter and Pro, 10,000 on Ultra. A larger declaration is clamped and the lint warns with window/clamped-to-cap. 5,000 buckets covers every daily bar but only about 17 days of 5m. For a long-window high, low or total, use ctx.summary(tf, { since: "52w" }), which has no bucket cap. See Indicator memory.
Why is my "all-time high" not all-time?
History stopped somewhere: at your plan's cap, your plan's history window, the instrument's first bar, or the data floor. The tooltip on the indicator row says which. Label the value "high since <date>", or use ctx.summary for the long window.
Why do values change when I scroll or refresh?
The warmup is too short for the state the script keeps, so each load starts from a different first bar. Raise the warmup to the rule above.
Timeframes
How do I read a higher timeframe?
Declare a handle at module scope and read it in onBar:
const daily = timeframe("1d");
output.line({ id: "prevHigh", color: "#f59e0b" });
export function onBar(ctx) {
const prevHigh = daily.last?.high;
if (prevHigh == null) return;
ctx.plot("prevHigh", prevHigh);
}ctx.security(...) and ctx.tf(...) in onBar were removed. A handle gives last (the last closed bucket), forming, bars(n), state and closedThisBar. See Timeframe handles and runOn.
How do I let the user pick the timeframe?
Use input.timeframe({ id, label, default, options }) and pass the returned handle to timeframe(). An input.select with timeframe options, or ctx.params.<id> at module scope, fails the lint because Chartnaut cannot tell which timeframes to load ahead of time.
Why can't I paint from inside a timeframe handler?
The handler runs once per bucket of its own timeframe and replays over the warmup, so a paint there would fire at the wrong cadence. Keep the value in the handler's state and paint from the chart onBar, gated on daily.closedThisBar.
What does runOn do?
It lists the chart timeframes the script may run on. On any other timeframe the chart shows "This chart is on 1d. Switch to 5m, 15m to preview it.", and running a definition over history or a study refuses with timeframe_not_allowed. runOn does not limit which timeframes the script reads through handles.
Definitions and events
Why did my definition produce zero events?
Three causes, and each looks the same from outside. The event id is not in events.declare (unknown_event_id). The emit has no label (missing_label). Or the condition never fired on that range. Read which one happened before changing the condition. See Emit not collected.
Why does the chart show events but the study sees none?
Events from a run over history are stored per definition version, per profile, per instrument and per timeframe. Saving the definition makes a new version, which starts with no events. Run the new version over history, or pin the study's flows.declare({ version }) to the version that has them.
Why do I get one event a day when I expected more?
The event declares oncePer: { keys: [...] }, which allows one emit per key tuple for the whole run. Remove it for per-bar events, or add a key that changes when you want a new event. Do not build your own dedupe map in ctx.accum.
Why are some emits missing at the start of the chart?
Emits during warmup bars are not recorded. The warmup exists to settle state, not to produce events.
What is a profile?
A named set of a definition's settings. The default profile uses the script's own defaults. Events are stored per profile, so running over history under one profile says nothing about another. A Forward Insight belongs to the profile its study ran under. See Versions and profiles.
Why does my profile say "Needs setup"?
A code change added settings the profile has no values for. It cannot run over history until you fill them in.
Dependencies
Why is my dependency's output empty?
Early bars have no value yet, the output key is wrong, or the dependency is pinned to a version that lacks that output. Guard null first. Then check the key: series are read as ctx.indicators.<as>.<outputKey>.value, never ctx.indicators.<as>.value. See Dependency unavailable.
Do dependencies follow the latest version?
No. Every indicators.declare and flows.declare pins an exact version, a positive integer. Built-in indicators are version 1. A newer version of the dependency does not reach your script until you change the pin.
Studies
Why did my study finish with no rows?
Check the errored-event count first: a bug that throws on every event gives a clean-looking empty report. Then check onEvent filters, and that ctx.event.flow is compared to the as alias from flows.declare, not the slug. Zero rows with zero errors is an answer about the market.
Why does ctx.candles return nothing in onFinish?
onFinish has no event clock, so event-relative APIs return empty. Measure in onEvent, ctx.collect the value, and read it back with ctx.collected() in onFinish.
Can a study see events after the current one?
Yes, within the run's range. Use ctx.flows.<as>.events({ after, filter }). Leave out limit: it is applied before the filter and can drop the match.
Forward Insights
How do I put a study on the chart?
Open the finished study run and click Attach to chart. The wizard ends with Save pin. Clicking an event on the chart then opens the Forward Insight for it. See Show studies on charts.
What does a study need before it can become a Forward Insight?
It must export scope(ctx), which returns the dimensions that describe the event from what was known when it fired. It must publish a result indexed by those dimensions, and measure something after the event in onEvent(ctx, scope), such as ctx.candles.forward(...). Outcomes are declared as role: "report" dimensions and never go in the tuple scope returns.
Does a Forward Insight re-measure the event I clicked?
No. It shows what happened after past events in the same cohort. The clicked event's own outcome is not known yet.
Why does the popover say "Not set up for this profile"?
The Forward Insight was saved under a different profile from the one the chart's definition uses. Use the Switch to button, or save a Forward Insight from a study run on this profile.
Why does the popover say "Can't use this pin on 5m"?
It was saved for another timeframe. Switch the chart, pick another Forward Insight, or save one for this timeframe.
Are small samples allowed?
Yes. A cohort with a small n still shows, with a limited-sample note. The count must be true; never pad it.
Limits and errors
Are budgets the same as AI credits?
No. Budgets stop a script that would freeze the chart or run too long. They cost nothing. AI credits pay for the Agent's model use.
What are the main limits?
Limit | Value |
|---|---|
One indicator calculation | warns at 2 s, stops at 30 s |
One handler call | 5 s |
Events one definition run can emit | 100,000 |
Layer paint per frame | 4 ms |
One study event | 15 s |
Event timeouts before a study run stops | 10 |
Whole study run | 10 minutes |
Study forward scan | 5,000 bars |
The full list is in Lint and error codes.
Where do I look up an error id?
Every rule id and run error is in Lint and error codes, with its severity and the fix. Symptoms without an id are in the troubleshooting overview.
