Script Format
Write a valid Pyne code script with the @pyne module marker, imports and main() function. Learn required elements, decorators and the optional edge profile.
A Pyne code script uses Python syntax in a .py file,
with a marker that enables Pyne’s execution rules. This page documents its 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 selects the edge language profile, a defined subset of Pyne’s syntax, imports, and calls. PyneIDE checks this profile and reports unsupported constructs. PyneComp emits the marker for converted scripts, and the beginner’s book uses it for programs written directly in Pyne.
PyneCore applies the same bar-by-bar language behavior and also analyzes edge
modules against the profile. Runtime rejection of profile diagnostics can be enabled
with PYNE_EDGE_STRICT=1; it is not enabled by default. A script running with the
default setting is therefore not proof that it meets the edge profile.
Use plain @pyne when a program deliberately needs broader Python integration.
Removing edge opts out of the subset contract; such a program must not be assumed
portable to a runtime or compiler that supports only edge code.
Types and methods in the edge profile
In an edge module, a class declares a user-defined type, Pine’s type:
- It has exactly one decorator,
@udt(or@dataclass), and no base class. - Its body holds annotated fields and methods. A docstring and
passare allowed too; any other statement is outside the profile. - A method is an undecorated
defwhose first parameter is the receiver, by conventionself. The receiver needs no annotation: its type is the class. A@staticmethod, a@classmethodor any other decorator on a method is outside the profile.
@udt
class PriceRange:
bottom: float = 0.0
top: float = 0.0
def midpoint(self) -> float:
return (self.bottom + self.top) / 2A method is Pine’s method midpoint(PriceRange this). Call it on the object,
current_range.midpoint(), or through the class, PriceRange.midpoint(current_range).
It can keep state like any function (ta.* calls, Series, Persistent), and the state
belongs to the call site, as on TradingView. The same method can also be written as a free
function marked @method whose first parameter is annotated with the type; PyneComp emits
that form for converted scripts. See User-Defined Types.
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, colorpynecore.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:
| Parameter | Default | Description |
|---|---|---|
title | '' | Display name |
overlay | False | Show on price chart (True) or separate pane |
format | inherit | Number formatting |
precision | None | Decimal digits |
max_bars_back | 0 | History buffer length (0 = auto) |
dynamic_requests | False | Allow dynamic request.*() calls |
na_bool | False | Keep 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):
| Parameter | Default | Description |
|---|---|---|
initial_capital | 100000 | Starting capital |
currency | currency.NONE | Account currency |
pyramiding | 0 | Max entries in same direction |
default_qty_type | percent_of_equity | Position sizing method |
default_qty_value | 100 | Default quantity |
commission_type | commission.percent | Commission calculation method |
commission_value | 0.0 | Commission amount |
slippage | 0 | Slippage in ticks |
margin_long | 100.0 | Long margin percentage |
margin_short | 100.0 | Short margin percentage |
calc_on_order_fills | False | Re-execute on fills |
calc_on_every_tick | False | Re-execute on every tick (live only) |
calc_on_every_history_tick | False | Re-execute on every historical tick |
use_bar_magnifier | True | Use LTF data for fill accuracy |
process_orders_on_close | False | Extra order processing after bar close |
close_entries_rule | 'FIFO' | Trade closing order |
risk_free_rate | 2.0 | For Sharpe ratio calculation |
close_by_id | False | One 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,
}