Appearance
Language extensions
What can you write in AmenoScript that Pine Script v6 won't take? You can pass a function where a value goes, call a saved script or a strategy as a function, give inputs stable ids and Optimizer ranges, and use a few more builtins and looser syntax. Each of these makes your script AmenoScript, so it runs leak-safe.
ameno
indicator("Up days")
var array<float> returns = array.new<float>()
array.push(returns, close / close[1] - 1)
if array.size(returns) > 20
array.shift(returns)
array<float> up = array.filter(returns, r => r > 0) // keep the rising days
float total = array.reduce(returns, 0.0, (sum, r) => sum + r)
plot(array.size(up), "Up days in the last 20")
plot(total * 100, "Sum of returns (%)")Lambdas
A lambda is a one-expression function written where it's used. It takes one of four forms: r => r > 0, (sum, r) => sum + r with two parameters, ([sym, len]) => ... to take a row of a list apart, and () => ta.rsi(close, 14) with none. Seven array functions take one:
| Function | Returns |
|---|---|
array.map(a, f) | A new array of f applied to each element. |
array.filter(a, f) | A new array of the elements where f is true. |
array.reduce(a, start, f) | f(acc, x) folded over the elements, from start. |
array.sort_by(a, f, order) | A new array sorted by f of each element, ascending unless order is order.descending. |
array.any(a, f) | Whether f is true for some element. |
array.all(a, f) | Whether f is true for every element. |
array.find(a, f) | The first element where f is true, or na. |
array.any, array.all and array.find stop at the first element that decides, and array.reduce over an empty array returns start.
Each is a method too, so returns.map(r => r * 100) works. A lambda reads anything visible where it's written. For a longer body, write a function and pass its name, in either form: array.map(returns, pct) or returns.map(pct). A function handed to array.reduce takes two parameters, the total so far and the element. A lambda goes straight into a call. It can't be kept in a variable, except as a row of a list written in the text.
Lists written in the text
array.map over a list written in your script does something a for loop can't: each row becomes a call site of its own. The list is either written inline in the call, array.map([5, 10, 20], n => ta.sma(close, n)), which works anywhere, a function's body included, or held by an untyped name at the top level, as COINS is below. Each row then keeps its own ta.* state and its own request, as if you had written the line once per row.
ameno
indicator("Two coins, two readings")
COINS = [
["CRYPTO:BTCUSD", 20, () => ta.rsi(close, 14)],
["CRYPTO:ETHUSD", 50, () => ta.mfi(hlc3, 14)]
]
// One request per row, each with its own symbol, length and reading
[avg, osc] = array.map(COINS, ([symbol, len, read]) =>
request.security(symbol, "1D", [ta.sma(close, len), read()]))
plot(array.get(avg, 0), "BTC SMA 20")
plot(array.get(osc, 0), "BTC RSI")
plot(array.get(osc, 1), "ETH MFI")The rules:
- A row's literals stay literals. A request's symbol written in the list is still a literal symbol where the lambda uses it. A function's name in a row stands where it's used in the same way.
- A row's other values run once, where the list is written. To run something somewhere else, such as inside a request, put a function in the row, as
readis above, and call it there. - A tuple per row gives an array per element.
[avg, osc] = array.map(...)gives one array of the first elements and one of the second. array.sizeof such a list is a constant.- The list stays a list only while
array.mapandarray.sizealone read it. A write, a reassignment orvarturns it into an ordinary array, and so does a type (array<float> L = [...]) or a name declared inside a function.
An array built at run time shares one state
Over an ordinary array, array.map is one function run in a loop. A ta.* call inside the lambda then keeps one state for every element, as it would in a for loop, and the engine warns. Write the list out in the text when each row needs its own state.
Functions that take functions
Your own functions can take a function as a parameter. Leave that parameter untyped (fn, not float fn), pass a lambda or a function's name, and call the parameter inside:
ameno
indicator("Smoothed, then rescaled")
double(float x) => x * 2
smooth(float src, int len, fn) => fn(ta.sma(src, len))
// x => x / open reads open as it stands at the call
float rescaled = smooth(close, 5, x => x / open)
float doubled = smooth(high, 3, double)
readDaily(string symbol, fn) => request.security(symbol, "1D", fn())
up() => close > close[1] ? 1.0 : 0.0
float btcUp = readDaily("CRYPTO:BTCUSD", up)
plot(rescaled, "Rescaled")
plot(doubled, "Doubled")
plot(btcUp, "BTC up day")Each call of a function that takes a function gets a copy of its own, with the handed function written in where the parameter stood. So each call's ta.* and requests keep their own state, and a library's function can work over whatever a script hands it. A list can hold functions too: array.map([x => x + 1, x => ta.sma(x, 3)], f => f(close)).
If a variable and a function share a name, the variable wins: the call is handed the variable's value, not the function. Overloads of a function that takes a function are told apart only by how many arguments a call passes, so of two overloads that take the same number, a call gets the first that fits.
There are no function values at run time. The engine rewrites every lambda into plain functions and loops before it runs anything, so a function can't be chosen by a condition, kept in an array that changes, or returned.
A script as a function
Any saved script can run inside another. Import it, then call .run() on it:
pine
//@version=6
indicator("SMA cross", overlay = true)
int fastLen = input.int(20, "Fast")
int slowLen = input.int(50, "Slow")
float fast = ta.sma(close, fastLen)
float slow = ta.sma(close, slowLen)
plot(fast, "Fast", color.teal)
plot(slow, "Slow", color.orange)ameno
indicator("Two crosses", overlay = true)
import Me/SmaCross/1 as cross
// Every plot, in order
[fast, slow] = cross.run()
// One plot by title, with an input set
float slow200 = cross.run("Slow", slowLen = 200)
// Any expression, read in SmaCross's scope: its fast and slow
bool up = cross.run(fast > slow)
float wkSlow = request.security(syminfo.tickerid, "1W", cross.run("Slow"))
plot(slow200, "SMA 200", color.gray)
plot(wkSlow, "Weekly SMA 50", color.purple)
bgcolor(up ? color.new(color.teal, 92) : na)| You write | You get |
|---|---|
s.run() | Every plot of the script, in the order it plots them: one value, or a tuple. |
s.run("Title") | The plot with that title. |
s.run(expr) | Any expression, read in the script's scope after its last statement. It reads the script's top-level names, never one declared inside an if or a function, and a name in it is the imported script's even where yours declares one of its own. |
s.run(..., name = value) | The same, with the input assigned to the variable name set to value. Any value works, a series included. |
An import finds a saved script by its name, SmaCross here. The author before the name and the version after it are there for TradingView's form and change nothing, and a library wins over a script of the same name. The import reads the saved script each time your script runs, so an edit to it reaches every script that imports it.
Each call keeps its own state, so cross.run(fastLen = 20) and cross.run(fastLen = 5) run side by side, while a call inside a for loop shares one state across the loop, as a ta.* call there does. An input you can set is one assigned to a variable, as slowLen is; any other keeps its default. Its inputs don't appear in your Settings, so you decide what to pass and what to show.
A call draws nothing. Its plots become values, and its hline, fill, bgcolor, barcolor, plotshape, plotchar, plotarrow, plotbar, plotcandle and alertcondition calls are dropped. A plot nothing reads is dropped too, with whatever only it computes.
A script with a function of its own named run keeps it. s.helper() calls one of the script's own functions, as long as that function reads nothing from the script's top level.
A strategy held in a function
A function can hold a strategy() of its own. Read it through request.security and it trades a simulated account of its own on that series, with whatever it returns coming back as a series:
ameno
indicator("Two breakouts on ETH")
breakout(int len) =>
strategy("Breakout", initial_capital = 10000,
default_qty_type = strategy.percent_of_equity,
default_qty_value = 100)
float hi = ta.highest(high, len)[1]
float lo = ta.lowest(low, len)[1]
if close > hi
strategy.entry("Long", strategy.long)
if close < lo
strategy.close("Long")
strategy.equity
// One account per request
float fast = request.security("ETH-USD", "1D", breakout(20))
float slow = request.security("ETH-USD", "1D", breakout(55))
plot(fast, "Breakout 20")
plot(slow, "Breakout 55")A saved strategy reads the same way through s.run, with no change to its text. Imported as trend, it reads as [eq, size] = request.security(syminfo.tickerid, "1D", trend.run(strategy.equity, strategy.position_size)). What comes back equals that strategy's own plot(strategy.equity) run alone, to the bit.
- One account per request. Two reads never share an account, so the same strategy with two lengths runs side by side.
- Its
strategy()arguments set that account. The script around it keeps its own broker, or none. - It needs a request. Called on the chart directly, it has no account to trade, and the run stops.
- One broker a run. A function holding a
strategy()can't call another one outside a request. request.eachtakes one,request.pairsdoesn't. Each member gets its own account. A pair has no single series to trade.book.*inside it reads na, with a warning.- A fill shows at the close of the bar it fills on, as on TradingView: an order placed at one close fills at the next bar's open, and
strategy.position_sizereads it at that bar's close. - It's an ordinary request for known time. Each bar of the account shows on the first chart bar closing at or after it's known, and inside, an order fills only on prices after it exists.
- The caller runs leak-safe, so a strategy that uses
lookahead_onis refused here, however it runs alone.
ameno
indicator("A broker with no request")
f() =>
strategy("Inner")
strategy.equity
plot(f()) // stops: read it through request.securityInputs with ids, ranges and checks
Three additions keep a script's settings working as the script changes:
ameno
strategy("Channel", overlay = true, process_orders_on_close = true)
int len = input.int(20, "Entry length", id = "entry_len",
sweep = [10, 60, 5], minval = 2)
int exitLen = input.int(10, "Exit length", id = "exit_len",
sweep = [5, 30, 5], minval = 2)
// Stops the run on a bad config
input.check(exitLen < len, "Exit length must be below Entry length")
if close > ta.highest(high, len)[1] and strategy.position_size == 0
strategy.entry("Long", strategy.long)
if close < ta.lowest(low, exitLen)[1]
strategy.close("Long")| Addition | What it does |
|---|---|
id = "..." on any input.* | The key saved settings and presets hold this input by, wherever it moves in the text and whatever its title becomes. Two inputs with one id stop the run. Write it as a literal. |
sweep = [from, to, step] on a number input | The Optimizer's range for it. The Optimizer opens with this input selected and walks it from from to to by step. No engine reads it. |
input.check(condition, message) | Runs on the first bar and stops the run with message unless condition is true there. A na fails it. |
An input without an id is found by its title, then by its position, so settings saved before you add one keep working.
More builtins
Besides the namespaces each with a page of its own (book, cs, mpt, report), AmenoScript has these builtins:
| Builtin | What it gives |
|---|---|
math.atan2(y, x), math.hypot(x, y) | The angle of a point, and its distance from the origin. |
str.trim_left(s), str.trim_right(s) | s without leading, or trailing, white space. |
str.char(s, i) | The character at i, or na past the ends. |
str.concat(a, b, ...) | Its arguments joined into one string. |
ta.zigzag(deviation, depth) | [pivot, direction, isHigh] for a zigzag that turns on a move of deviation percent. |
ta.ichimoku(conversion, base, span2, displacement) | [tenkan, kijun, senkouA, senkouB, chikou], defaults 9, 26, 52, 26. |
ta.md(source, length) | Mean absolute deviation, the same as ta.dev. |
ta.ts_rank, ta.cov, ta.beta | Rolling rank, covariance and beta. See Cross-section. |
request.universe, request.each, request.pairs | See Universes and Each and pairs. |
request.age, request.status, request.quality, max_age = on request.security | How fresh and how whole a series is. See Known time. |
request.funding | A perpetual's funding rate. See Facts. |
syminfo.unit, syminfo.cadence, syminfo.lag | What a series measures, how often it prints, and how late it's published. See Known time. |
input.hline(defval, title) | A price input, as input.price. |
dayofyear | The bar's day of the year. |
qty_step on strategy() | Rounds each order's size down to a multiple of it. |
close_on_end_of_data on strategy() | Whether to close what's open at the last bar, true by default. |
The reference has each one's parameters.
Syntax TradingView refuses
AmenoScript also accepts some syntax that TradingView's compiler rejects. Each of these makes a script AmenoScript too, which matters if you meant to write Pine: Pine v6 strict mode reports each one.
| You can write | TradingView requires |
|---|---|
x == na | na(x) |
float y = cond on one line and ? a : b on the next | The whole ternary on one line. |
A line that starts with +, and or another operator | The operator at the end of the line before. |
[a, b] := [b, a] | Separate assignments. |
| A function that calls itself | No recursion. |
array<array<float>>, map<string, array<int>> | A user type to wrap the inner collection. |
var x = na, x = na with no type | var float x = na |
plot, hline, fill, bgcolor or barcolor inside an if or a loop | Those calls at the top level. |
| A function parameter or type field defaulting to an expression | A literal or a builtin. |
| A type with no fields, or a method with no parameter | At least one. |
5 / 0 written as a literal | No literal division by zero. |
| A variable declared twice in one block | One declaration. |
Pine compatibility has the rest of what Pine mode checks.