Script Format

A PyneCore script is a standard .py file with a specific structure. This page documents the required elements and conventions.

Minimal Example

"""@pyne"""
from pynecore.lib import script, close


@script.indicator("My Indicator")
def main():
    return {"close": close}

1. Magic Comment

Every PyneCore script must begin with a docstring that starts with @pyne:

"""@pyne"""

The docstring can also serve as the script’s documentation — any text after @pyne is treated as a description:

"""
@pyne
RSI Mean Reversion Strategy

This strategy enters long positions when RSI crosses above the oversold level
and exits when RSI crosses below the overbought level.
"""

This marker tells the import hook to apply AST transformations (Series, Persistent, function isolation, etc.) before executing the module. Without it, the file is treated as regular Python.

The edge Variant

You may also encounter scripts whose docstring starts with @pyne edge:

"""@pyne edge"""

The extra token marks code that stays within a strict, Pine-equivalent subset of Python. The PyneComp compiler emits it automatically, since compiled Pine Script always falls within this subset. PyneCore runs @pyne edge scripts exactly like any other Pyne code — the token changes nothing at runtime. It exists to enable future tooling that relies on the subset guarantee (for example, a possible Pyne-to-Pine conversion). If you edit such a script and want the full power of Python, simply remove the edge word from the docstring.

2. Imports

Import type annotations from pynecore and library functions from pynecore.lib:

from pynecore.types import Series, Persistent, IBPersistent
from pynecore.lib import script, input, close, open, high, low, volume, ta, strategy, plot, color

pynecore.types provides the type annotations. pynecore.lib provides all runtime functions, built-in variables, and namespaces (ta, math, strategy, color, etc.).

3. Script Decorator

The main() function must be decorated with one of:

@script.indicator

For scripts that calculate and display values.

@script.indicator("SMA Indicator", overlay=True)
def main():
    ...

Key parameters:

ParameterDefaultDescription
title''Display name
overlayFalseShow on price chart (True) or separate pane
formatinheritNumber formatting
precisionNoneDecimal digits
max_bars_back0History buffer length (0 = auto)
dynamic_requestsFalseAllow dynamic request.*() calls
na_boolFalseKeep Pine v4/v5’s three-state bool (see bool)

@script.strategy

For scripts that simulate trading.

@script.strategy(
    "MA Crossover",
    overlay=True,
    initial_capital=10000,
    commission_type=strategy.commission.percent,
    commission_value=0.1,
)
def main():
    ...

Key parameters (in addition to indicator parameters):

ParameterDefaultDescription
initial_capital100000Starting capital
currencycurrency.NONEAccount currency
pyramiding0Max entries in same direction
default_qty_typepercent_of_equityPosition sizing method
default_qty_value100Default quantity
commission_typecommission.percentCommission calculation method
commission_value0.0Commission amount
slippage0Slippage in ticks
margin_long100.0Long margin percentage
margin_short100.0Short margin percentage
calc_on_order_fillsFalseRe-execute on fills
calc_on_every_tickFalseRe-execute on every tick (live only)
calc_on_every_history_tickFalseRe-execute on every historical tick
use_bar_magnifierTrueUse LTF data for fill accuracy
process_orders_on_closeFalseExtra order processing after bar close
close_entries_rule'FIFO'Trade closing order
risk_free_rate2.0For Sharpe ratio calculation
close_by_idFalseOne close() order per id (v4/v5)

@script.library

For reusable library modules.

@script.library("My Utils", overlay=True)
def main():
    ...

Exported functions may reference library constants, but must not capture non-constant variables from the module or library main() scope. The AST validator also checks helpers and default arguments reached by an export. Create per-call state and drawings inside the exported function, or pass them as parameters. This check applies to handwritten Pyne and to libraries run directly as the main script.

When a library is imported, drawing constructors and copies called by its main() (including its helper calls) return typed na handles without allocating or registering drawings. Setters and deletion on those handles have no effect. The same library draws normally when run as the main script, and an exported function called by the importing script can create normal drawings. This suppression is separate from the request.security execution context.

4. The main() Function

The main() function is called once per bar during execution. It receives input parameters and contains all script logic.

Input Parameters

Configurable parameters are declared as function arguments with input.*() defaults:

@script.indicator("RSI", overlay=False)
def main(
        length: int = input.int(14, title="RSI Length", minval=1),
        overbought: int = input.int(70, title="Overbought"),
        oversold: int = input.int(30, title="Oversold"),
        src: Series[float] = input.source(close, title="Source"),
):
    rsi_value = ta.rsi(src, length)
    ...

See Input Functions for all available input types.

Return Value

main() can optionally return a dict of named values to plot:

def main():
    fast = ta.sma(close, 9)
    slow = ta.sma(close, 21)
    return {"Fast MA": fast, "Slow MA": slow}

Alternatively, use plot() calls within the function body. Both approaches can be combined.

5. Reserved Names

The transform adds names of its own to the script, so two kinds of identifier are reserved and rejected with a SyntaxError at load time:

  • any identifier containing a middle dot (·);
  • the plain double-underscore names the transform emits, such as __state__, __bool1__, __cmp3__ or any __pyne_<name>__.

Other double-underscore names, including the ones PyneComp writes into compiled scripts (__block_result__, __input_1__, …), are allowed. Strings and comments are not checked. See AST Transformations for the complete list.

Complete Example

"""@pyne"""
from pynecore.types import Series, Persistent
from pynecore.lib import script, input, close, ta, strategy, plot, color


@script.strategy(
    "RSI Mean Reversion",
    overlay=True,
    initial_capital=10000,
    commission_type=strategy.commission.percent,
    commission_value=0.1,
)
def main(
        rsi_length: int = input.int(14, title="RSI Length", minval=1),
        oversold: int = input.int(30, title="Oversold Level"),
        overbought: int = input.int(70, title="Overbought Level"),
):
    rsi: Series[float] = ta.rsi(close, rsi_length)

    trade_count: Persistent[int] = 0

    if ta.crossover(rsi, oversold):
        strategy.entry("Long", strategy.long)
        trade_count += 1

    if ta.crossunder(rsi, overbought):
        strategy.close("Long")

    plot(rsi, "RSI", color=color.purple)

    return {
        "Overbought": overbought,
        "Oversold": oversold,
    }