Skip to main content
ATK Pine Script®

Visual Anti-Patterns

Wrong vs right for visual authoring — choosing the wrong visual family, mixing static and bridge fill identities, and stale attrs coordinates.

Wrong vs Right for Common Authoring Mistakes#

Choosing the wrong visual family#

# Wrong: create object machinery for a simple row-aligned line.
def build_visuals(frame, params=None, ctx=None):
    return ctx.line.new(key="ema_line", x1=0, y1=100, x2=10, y2=101)


# Right: if the frame already has one value per row, stay with plot(...).
plot("ema_fast", key="ema_fast", title="EMA Fast", color="#00c853", width=2)

Mixing static and bridge fill identities#

# Wrong: trying to fill between static keys with the bridge helper.
# ctx.atk.fill_between(line1="bb_upper", line2="bb_lower", ...)

# Right: bridge fills between bridge line keys.
ctx.atk.plot_line(key="fast_line", source="fast", color="#00c853")
ctx.atk.plot_line(key="slow_line", source="slow", color="#f23645")
ctx.atk.fill_between(key="trend_fill", line1="fast_line", line2="slow_line", fill_alpha=24)

Computing inside build_visuals#

# Wrong: TA computation inside the render stage.
def build_visuals(frame, params=None, ctx=None):
    ema = ta.ema(frame["close"], 20)   # DO NOT do this here
    last_ema = float(ema.iloc[-1])
    return ctx.label.new(key="ema_label", x=int(frame.iloc[-1]["index"]), y=last_ema, text="EMA")


# Right: compute in build_indicator_frame, read in build_visuals.
def build_indicator_frame(df, params=None):
    frame = df.copy().reset_index(drop=True)
    frame["ema_fast"] = ta.ema(frame["close"], 20)
    return frame

def build_visuals(frame, params=None, ctx=None):
    last = frame.iloc[-1]
    return ctx.label.new(
        key="ema_label",
        x=int(last["index"]),
        y=float(last["ema_fast"]),
        text="EMA",
    )

attrs Are for Static Config, Not Frozen Coordinates#

The safe rule is simple: colors, headers, labels, and other stable config can live in frame.attrs. Coordinates, tail-derived anchors, and current slice geometry should be derived from the frame passed into build_visuals. Otherwise historic or sliced rendering can reuse stale payloads.

Never store bar-index coordinates or price levels in frame.attrs. Those values change with each update. Only store stable display config — colors, font sizes, header strings, style tokens.