Skip to main content
ATK Pine Script®

Starter Examples

The safest first examples to copy — indicator starter, strategy starter, and visual starter for ATK PyneScript V6.

Starter Examples#

If you are starting from zero, these three scripts are the safest to copy because they map directly to the current runtime contract.

FileWhat it demonstratesBest useDownload
pynescript_indicator_starter.pyindicator, input.int, input.source, ta.rsi, hline.First runnable indicator example.Download
pynescript_strategy_starter.pystrategy, EMA crossover, mapped execution fields, build_mapped_trade_frame.Canonical strategy starter.Download
pynescript_visual_indicator_starter.pyplot, plotshape, hline, static frame.attrs payload for box, label, line objects.Best introduction to render-only build_visuals.Download

Indicator Starter#

Use this when you want the smallest correct indicator scaffold with inputs, TA, and a single computed output series. All series computation belongs in build_indicator_frame. Static visuals like hline can be declared at module scope.

# @name: pynescript_indicator_starter
import pandas as pd

from source import indicator, input, ta, hline

indicator("Pyne Indicator Starter", overlay=False)
length = input.int(14, title="Length", key="length")
source_type = input.source("close", title="Source", key="source_type")
hline(70, title="Overbought", color="#ff6b6b")
hline(30, title="Oversold", color="#51cf66")


def build_indicator_frame(df: pd.DataFrame, params: dict | None = None) -> pd.DataFrame:
    frame = df.copy().reset_index(drop=True)
    p = {"length": int(length), "source_type": str(source_type)} | dict(params or {})
    src_name = str(p.get("source_type", "close"))
    src = frame[src_name] if src_name in frame.columns else frame["close"]
    frame["value"] = ta.rsi(src, int(p.get("length", 14)))
    return frame

Download indicator starter


Strategy Starter#

Use this as the canonical starting point for trade-frame strategies. build_signal_frame computes signals and canonical execution fields. build_trade_frame stays mapping-only by delegating to build_mapped_trade_frame.

# @name: pynescript_strategy_starter
import numpy as np
import pandas as pd

from source import strategy, input, ta, build_mapped_trade_frame

strategy("Pyne Strategy Starter", overlay=True, process_orders_on_close=True)
fast_period = input.int(10, title="Fast", key="fast_period")
slow_period = input.int(21, title="Slow", key="slow_period")
trade_qty = input.float(1.0, title="Trade Qty", key="trade_qty")
size_pct = input.float(0.0, title="Size %", key="size_pct")


def build_signal_frame(df: pd.DataFrame, params: dict | None = None) -> pd.DataFrame:
    frame = df.copy().reset_index(drop=True)
    p = {
        "fast_period": int(fast_period),
        "slow_period": int(slow_period),
        "trade_qty": float(trade_qty),
        "size_pct": float(size_pct),
    } | dict(params or {})
    ema_fast = ta.ema(frame["close"], int(p["fast_period"]))
    ema_slow = ta.ema(frame["close"], int(p["slow_period"]))
    frame["ema_fast"] = ema_fast
    frame["ema_slow"] = ema_slow
    frame["buy_signal"] = ta.crossover(ema_fast, ema_slow).fillna(False)
    frame["sell_signal"] = ta.crossunder(ema_fast, ema_slow).fillna(False)
    frame["entry_side"] = np.where(frame["buy_signal"], "BUY", np.where(frame["sell_signal"], "SELL", ""))
    frame["entry_price"] = frame["open"]
    frame["quantity"] = float(p.get("trade_qty", 0.0) or 0.0)
    frame["size_pct"] = float(p.get("size_pct", 0.0) or 0.0)
    return frame


def build_trade_frame(signal_df: pd.DataFrame, params: dict | None = None, styles: dict | None = None) -> pd.DataFrame:
    return build_mapped_trade_frame(signal_df)

Download strategy starter


Visual Indicator Starter#

Use this when row-aligned plots are not enough and you need object visuals such as boxes, labels, or lines derived from the computed frame. Keep dynamic calculation in build_indicator_frame, store static object config in frame.attrs, then create visuals in a render-only build_visuals.

# @name: pynescript_visual_indicator_starter
import pandas as pd

from source import indicator, input, ta, plot, plotshape, hline

indicator("Pyne Visual Indicator Starter", overlay=True)
length = input.int(20, title="Length", key="length")

plot("ema_fast", key="ema_fast", title="EMA Fast", color="#00c853", width=2)
plot("ema_slow", key="ema_slow", title="EMA Slow", color="#ff6d00", width=2)
hline(100, key="price_ref", title="Reference", color="#9e9e9e")
plotshape("buy_signal", key="buy_markers", location="belowbar", style="arrow_up", color="#00c853", text="BUY")


def build_indicator_frame(df: pd.DataFrame, params: dict | None = None) -> pd.DataFrame:
    frame = df.copy().reset_index(drop=True)
    p = {"length": int(length)} | dict(params or {})
    fast_length = int(p.get("length", 20))
    slow_length = max(fast_length + 5, 2)
    frame["ema_fast"] = ta.ema(frame["close"], fast_length)
    frame["ema_slow"] = ta.ema(frame["close"], slow_length)
    frame["buy_signal"] = ta.crossover(frame["ema_fast"], frame["ema_slow"]).fillna(False)
    if not frame.empty:
        last = frame.iloc[-1]
        anchor = frame.iloc[max(len(frame) - 10, 0)]
        frame.attrs["visual_indicator_payload"] = {
            "box": {
                "left": int(anchor["index"]), "top": float(last["high"] + 0.5),
                "right": int(last["index"]), "bottom": float(last["low"] - 0.5),
                "text": "ACTIVE RANGE", "bgcolor": "rgba(41,98,255,0.12)", "border_color": "#2962ff",
            },
            "label": {"x": int(last["index"]), "y": float(last["close"]),
                      "text": "LAST", "color": "#2962ff", "style": "label_down"},
            "line": {"x1": int(anchor["index"]), "y1": float(anchor["close"]),
                     "x2": int(last["index"]), "y2": float(last["close"]),
                     "color": "#ffd600", "text": "TREND"},
        }
    else:
        frame.attrs["visual_indicator_payload"] = {}
    return frame


def build_visuals(frame: pd.DataFrame, params: dict | None = None, ctx=None):
    if frame is None or frame.empty or ctx is None:
        return None
    payload = dict(frame.attrs.get("visual_indicator_payload") or {})
    if not payload:
        return None
    ctx.box.new(key="last_range_box", **dict(payload.get("box") or {}))
    ctx.label.new(key="last_close_label", **dict(payload.get("label") or {}))
    return ctx.line.new(key="trend_hint", **dict(payload.get("line") or {}))

Download visual indicator starter


Core API Recipes#

Annotated Indicator Declaration#

import pandas as pd
from source import indicator, input, plot, ta

indicator("Annotated EMA Overlay", overlay=True, precision=4, max_bars_back=200)
length = input.int(34, title="EMA Length", key="length", minval=1)
source_type = input.source("close", title="Source", key="source_type")
plot("ema_value", key="ema_value", title="EMA", color="#00c853", width=2)


def build_indicator_frame(df: pd.DataFrame, params: dict | None = None) -> pd.DataFrame:
    frame = df.copy().reset_index(drop=True)
    merged = {"length": int(length), "source_type": str(source_type)} | dict(params or {})
    source_name = str(merged.get("source_type", "close") or "close")
    source_series = frame[source_name] if source_name in frame.columns else frame["close"]
    frame["ema_value"] = ta.ema(source_series, int(merged.get("length", 34) or 34))
    return frame

Annotated Library Declaration#

from source import library, import_library

library("Annotated Utility Library", version=1, overlay=False, description="Reusable helper functions")


def clamp_length(value, minimum=1):
    return max(int(value), int(minimum))


# In another script, import only the exports you want.
utils = import_library("Annotated Utility Library@1", exports=["clamp_length"])
safe_length = utils.clamp_length(0, 2)

See also: