Language Reference
The syntax of Vaanam Script: how to write the script itself. For the built-in
functions (ta.*, strategy.*, …) see Built-in Namespaces.
Script header
Every script starts with a version directive and a strategy(...) declaration:
//@version=1
strategy("My Strategy", timeframe="1D")
//@version=1— the language version. Put it on line 1.strategy("Name", ...)— required exactly once. Optionaltimeframekwarg pins the strategy to a specific resolution regardless of the UI selection:- Canonical:
"1M"(1-minute),"5Min","10Min","15Min","30Min","1H","4H","1D","1W","1Month". - Aliases like
"daily","weekly","1h"are accepted. - Note:
"1M"is 1-minute,"1Month"is monthly. Don't write bare"M".
- Canonical:
Comments
// single-line comment (Pine-style)
# single-line comment (Python-style — also accepted)
/* multi-line
comment */
x = ta.rsi(close, 14) # mid-line comments work too
Both // and # work. The //@version=N directive is only recognized with
the // marker.
Variables
length = 14
trend_up = close > ta.sma(close, 200)
- Plain
name = exprdeclares a variable. There is nolet/const. - Ordinary variables are bar-local — they reset every bar.
var name = exprdeclares a persistent variable that survives across bars. The initializer runs once (on the first bar); after that the variable keeps whatever value it was last assigned.
var highest_since_entry = na
if position.size > 0:
highest_since_entry = math.max(highest_since_entry, high)
Forgetting var is the most common mistake — without it, your "running" value
resets to its initializer every bar. If something should remember a value
between bars, declare it with var.
Operators
| Category | Operators |
|---|---|
| Arithmetic | + - * / % ** |
| Comparison | < > <= >= == != |
| Logical | and or not |
| Indexing | series[N] |
Standard precedence: ** binds tightest among arithmetic; not tighter than
and, and tighter than or. Use () to group.
in_range = x > 0 and x < 100 # ✓
in_range = 0 < x < 100 # ✗ chained comparisons are NOT supported
Booleans are lowercase true / false (not True/False).
Series and indexing
close, open, high, low, volume are series — a value per bar.
Index them to look back:
closeorclose[0]— the current bar's close.close[1]— the previous bar's close.close[N]— N bars ago. If N exceeds available history, you getna.
In a comparison or arithmetic context a series automatically resolves to its
current value, so close > 100 means close[0] > 100.
Control flow
Indentation (4 spaces) defines blocks. Don't mix tabs and spaces.
if / elif / else
if rsi < 30:
strategy.enter_long(size_pct=100)
elif rsi > 70:
strategy.close_all()
else:
# do nothing
x = 0
Use elif, not else if.
Loops
for i in range(0, 5):
# i goes 0,1,2,3,4
total = total + close[i]
while cond:
...
Loops are capped per bar to keep backtests fast and safe.
Conditional expression (ternary)
Python-style <then> if <cond> else <else>, as an expression (it produces
a value):
size = 100 if rsi < 30 else 10
Use it to pick a value — not as a substitute for control flow. For actions with
side effects (strategy.enter_long, etc.), use a full if statement.
# ✗ wrong — don't drive actions with a ternary
strategy.enter_long(size_pct=100) if rsi < 30 else strategy.close_all()
# ✓ right
if rsi < 30:
strategy.enter_long(size_pct=100)
else:
strategy.close_all()
Vaanam Script uses the Python if/else ternary, not Pine's cond ? a : b.
Functions
Define reusable helpers at the top level with def:
def momentum(price, fast_len, slow_len):
fast = ta.sma(price, fast_len)
slow = ta.sma(price, slow_len)
return (fast - slow) / slow * 100
score = momentum(close, 10, 50)
In v1, functions return scalars (a single number/boolean), not series.
Tuple destructuring
Multi-output indicators return tuples; unpack them with a comma assignment:
upper, middle, lower = ta.bbands(close, 20, 2)
macd, signal, hist = ta.macd(close, 12, 26, 9)
The na value
na means "no value" — an indicator that hasn't warmed up, or a field that
doesn't exist (like position.entry_price when flat).
- Arithmetic with
nayieldsna; comparisons withnayieldfalse. na == naistrue— use it to test forna:
if not (prev_value == na):
# prev_value has a real value
...
What's intentionally not in the language
To keep v1 small and safe, these do not exist: import/modules, classes,
list/dict literals ([...], {...}), list comprehensions, file or network
access, and Pine-specific constructs like request.security (use
htf instead) or ?: ternaries.