Skip to main content
ATK Pine Script®

Stage Contracts

Stage ownership rules for visual authors — what each stage owns and must not do across build_indicator_frame, build_signal_frame, build_trade_frame, and build_visuals.

Stage Contracts for Visual Authors#

StageOwnsMust not do
build_indicator_frameIndicator computation, prepared visual columns, static attrs config.Render objects directly.
build_signal_frameStrategy computation, MTF helpers, mapped execution intent.Delay risk, entries, or resampling into later stages.
build_trade_frameMapping or delegation to build_mapped_trade_frame(...).Run TA, request calls, or invent strategy logic.
build_visualsRendering from prepared data and current frame slice.Compute indicators, mutate input frames, or depend on stale attrs-only coordinates.

Practical test: if you could delete build_visuals and still preserve all computed columns and strategy intent, your stage split is probably correct. If deleting it removes calculations, those calculations belong earlier.

What Each Stage Means for Visual Authors#

build_indicator_frame#

This is where all computation happens. By the time build_visuals runs, every value it needs should already exist as a column or in frame.attrs. Prepare visual columns here — signal booleans, anchor prices, display labels — so build_visuals stays read-only.

build_visuals#

This stage is strictly a render pass. It reads the last rows of the frame, unpacks config from frame.attrs, and assembles ctx.* or ctx.atk.* intents. It must not call ta.*, resample, mutate the frame, or perform any computation that belongs in the frame builder.

frame.attrs for static config#

Store colors, headers, style tokens, and other stable configuration in frame.attrs inside build_indicator_frame. Derive coordinates, tail anchors, and slice-specific geometry from the live frame slice inside build_visuals.

# build_indicator_frame — store style config in attrs
frame.attrs["label_style"] = {"color": "#2962ff", "style": "label_down"}

# build_visuals — read attrs for config, frame for coordinates
def build_visuals(frame, params=None, ctx=None):
    if frame is None or frame.empty or ctx is None:
        return None
    last = frame.iloc[-1]
    style = dict(frame.attrs.get("label_style") or {})
    return ctx.label.new(
        key="signal_label",
        x=int(last["index"]),
        y=float(last["low"]),
        text="SIGNAL",
        **style,
    )