Navy: core concepts
Navy: core concepts
Series and history, variables and state, types and na, conditions and loops, functions, inputs — and what a script can read.
Series and history

Every value is a series: it has a value on every bar; [k] reads it k bars back.
![Six bars with close on the bar being computed, close[1] on the bar before it and so on back to close[5]; before the first bar the value is na](assets/img/docs-new/navy/history-index.en.webp)
[k]works on built-in series, Wintrading's per-bar data (liq.long[1]), top-level variables (ema[1]), expressions ((high - low)[1]);kmay be a variable:close[i].- No history of a call —
const hh = ta.highest(high, 20), thenhh[1], notta.highest(high, 20)[1](NV2403) — nor of a block variable, a string, a color, an object. - A variable
kreaches 500 bars at first (maxBarsBack), then grows to 20 000; a constant — up to 20 000.
//@navy 2.0
overlay('20-bar breakout')
const hh = ta.highest(high, 20) // the highest high of the last 20 bars, this one included
const top = hh[1] // the same value one bar ago: the previous 20 bars
const breakout = close > top
const first = breakout && !breakout[1] // the first bar of a breakout, not every bar above
plot(top, { title: 'Previous 20-bar high', color: color.gray, style: 'step' })
marker.below(first, 'Breakout', { shape: 'label', color: color.teal })Variables and state
Four declarations; they differ in what reaches the next bar.

| Declaration | What it is | To the next bar |
|---|---|---|
const x = … | Computed on every bar; cannot be reassigned | no |
let x = … | Computed on every bar; may change during the bar | no |
var x = … | Set on the first bar, then keeps its value; rolled back before every update of the live bar | yes |
varip x = … | Like var, never rolled back: counts every update | yes, updates too |
=,+=,-=,*=,/=,%=;++/--only in aforheader.var total = 0is a float; a whole-number counter:var n: int = 0.- A first use without
const/let/var— NV2003; a name declared twice in a block — NV2302. - Pine's
var float x = na,var line l = nawork.
//@navy 2.0
indicator('Green streak')
const green = close > open // const: computed on every bar, never reassigned
var streak = 0 // var: set once, then carried from bar to bar
if (green) {
streak += 1
} else {
streak = 0
}
let col = color.gray // let: computed on every bar, may change below
if (streak >= 3) {
col = color.teal
}
plot(streak, { title: 'Green bars in a row', style: 'columns', color: col })Types and na
Types are inferred; write one only where it matters: let x: float = na, var l: line = na.
| Type | Values | Can be na |
|---|---|---|
float | A number (64-bit) | yes |
int | A whole number | yes |
bool | true, false | never |
string | Text: 'abc' or "abc" | no |
color | color.red, '#ff9800', color.rgb(…) | no |
line, box, label | A drawing object | yes: none yet |
array<T>, map<K, V> | A list, a dictionary | yes: none yet |

- int → float freely; back only with
int(x),math.floor,math.ceil.7 / 2is 3.5. - Conditions are bool:
if (!na(x)), notif (x)for a number (NV2101). - na: test
na(x)(x == nais always false);nz(x, y),x ?? y;fixnan(x)— the last non-na. ta.*lengths: a number, an input or+ - *of them — not a call likeint(len / 2)(NV2201).
//@navy 2.0
indicator('Distance from the average, %', { precision: 2 })
const len = input.int('Length', 50, { min: 1 })
const avg = ta.sma(close, len) // na until there are len bars
const dist = (close - avg) / avg * 100 // na in, na out: no value on those bars
const above = dist > 0 // a bool is never na: false where dist is na
var barsAbove: int = 0 // an int counter (a plain 0 would make a float)
barsAbove = above ? barsAbove + 1 : 0
plot(dist, { title: 'Distance, %', style: 'area', base: 0, color: color.teal, colorDown: color.red })
marker.at(barsAbove == 20 ? dist : na, '20', { color: color.teal, size: 'small' })Conditions and loops
if (cond) { … } else if (cond2) { … } else { … }andcond ? a : b. Noswitch— useelse if.for (let i = 0; i < n; i++) { … }(alsoi--,i += 2),while (cond) { … },break,continue.- Braces, or a body indented as in Pine.
- Up to 1 000 000 loop iterations per bar (
fuel: up to 10 000 000); an endless loop stops the script (NV4001).

//@navy 2.0
indicator('Up closes in the last N bars')
const n = input.int('Bars', 10, { min: 1, max: 100 })
let ups = 0
for (let i = 0; i < n; i++) {
if (close[i] > open[i]) {
ups += 1
}
}
// ta.* at the top level, on every bar; the condition only uses the result
const rsi = ta.rsi(close, 14)
const strong = ups >= n * 0.7 && rsi > 60
plot(ups, { title: 'Up closes', style: 'columns', color: strong ? color.teal : color.gray })Functions
A function gives a calculation a name, to use it more than once.
//@navy 2.0
indicator('Z-score')
// How far a value is from its average, in standard deviations.
function zscore(src: float, len: int): float {
const mean = ta.sma(src, len)
return (src - mean) / ta.stdev(src, len)
}
const zPrice = zscore(close, input.int('Length', 20, { min: 2 }))
const zVolume = zscore(volume, 50) // its own averages: the two calls share no state
plot(zPrice, { title: 'Price', color: color.blue, width: 2 })
plot(zVolume, { title: 'Volume', color: color.orange })
hline(2, { color: color.gray })
hline(-2, { color: color.gray })- At the top level, before use:
function name(a: float, n: int = 14): float { … }; onereturnvalue. - Each call has its own state: the two
zscorecalls share nota.sma. - A top-level variable that changes per bar comes in as a parameter (NV2502). No recursion (NV2501).
- Tuples come from built-ins:
const [macd, signal, hist] = ta.macd(close, 12, 26, 9).
Arrays, maps and your own types
Several values at once: the last swings, the levels still unbroken.
//@navy 2.0
overlay('Highest of the last swings')
const len = input.int('Swing length', 5, { min: 1 })
const keep = input.int('Swings to keep', 5, { min: 1, max: 50 })
const ph = ta.pivothigh(high, len, len)
var highs = array.new<float>(0) // var: the list lives from bar to bar
if (!na(ph)) {
highs.push(ph)
if (highs.size() > keep) {
highs.shift() // drop the oldest
}
}
plot(highs.size() > 0 ? highs.max() : na, { title: 'Highest swing', color: color.red, style: 'step' })array.new<float>(0),array.from(1, 2, 3):push,shift,get(i),size(),max()… (all).map.new<string, float>():put,get,has,keys().type Pivot = { price: float, bar: int };enum Mode { Fast, Slow }→input.enum.- In
varit lives from bar to bar; inlet/constit is new on every bar.a.copy()copies. - Up to 100 000 elements; an index outside stops the script (NV4003).
Inputs (settings)
Every number a trader may tune is an input: the title first, the default second (the reverse of Pine).

| Input | In the settings window | Gives |
|---|---|---|
input.int(title, 14, { min, max, step, options }) | A number field; with options a drop-down | int |
input.float(title, 1.5, { min, max, step, options }) | A number field; with options a drop-down | float |
input.bool(title, true) | A checkbox | bool |
input.string(title, 'EMA', { options }) | A text field; with options a drop-down | string |
input.color(title, color.orange) | A color picker | color |
input.source(title, close) | A drop-down of price series: close, hl2, ohlc4 … | a series |
input.timeframe(title, '4h') | A drop-down of timeframes | string |
input.enum(title, Mode.Fast) | A drop-down of the enum's members | the enum |
tooltip— a hint;group— a section; titled''— in the previous row; titles may repeat across groups.- Values are saved by title: renaming an input resets it.
- An input works where a constant is needed: a
ta.*length, asecurity()timeframe.
//@navy 2.0
overlay('Average with a band')
const src = input.source('Source', close, { group: 'Average' })
const len = input.int('Length', 20, { min: 1, max: 500, group: 'Average' })
const kind = input.string('Type', 'EMA', { options: ['EMA', 'SMA', 'HMA'], group: 'Average' })
const lineColor = input.color('', color.orange, { group: 'Average' }) // '' sits in the row of «Type»
const showBand = input.bool('Show band', true, { group: 'Band' })
const mult = input.float('Width, × ATR', 1.5, { min: 0.1, step: 0.1, group: 'Band', tooltip: 'Half-width of the band in ATRs' })
const tf = input.timeframe('Trend timeframe', '4h', { group: 'Trend' })
const ema = ta.ema(src, len)
const sma = ta.sma(src, len)
const hma = ta.hma(src, len)
const ma = kind == 'EMA' ? ema : kind == 'SMA' ? sma : hma
const atr = ta.atr(14)
const trendUp = security(tf, () => close > ta.ema(close, 50))
plot(ma, { title: 'Average', color: lineColor, width: 2 })
const upper = plot(showBand ? ma + atr * mult : na, { title: 'Upper', color: color.new(lineColor, 0.4) })
const lower = plot(showBand ? ma - atr * mult : na, { title: 'Lower', color: color.new(lineColor, 0.4) })
fill(upper, lower, { color: trendUp ? color.new(color.teal, 0.08) : color.new(color.red, 0.08) })What a script can read

| Name | What it is | Type |
|---|---|---|
open, high, low, close | Prices of the bar; close is the last price on the live bar | float |
volume | Volume of the bar | float |
hl2, hlc3, ohlc4, hlcc4 | (high + low) / 2 and the other averaged prices | float |
time | The bar's open time, ms since 1970, UTC | float |
bar_index | The bar's number: 0 on the first loaded bar | int |
barstate.isfirst, .islast, .ishistory, .isrealtime, .isnew, .isconfirmed | The first bar; the live one; a closed bar; a bar updated live; its first run; its last run | bool |
syminfo.ticker, syminfo.ticksize (syminfo.mintick) | The symbol (BTCUSDT) and its tick size | fixed |
timeframe.ms | The length of one bar, ms | int |
vol,liq,oi,fp,book— per bar with history;walls— a live list: read it inif (barstate.islast). Fields: reference; units: Scripts → Data.- No data —
na:nz()it.
//@navy 2.0
indicator('Volume delta, $M', { precision: 2 })
const delta = nz(vol.delta) * close / 1000000 // taker buys − sells of the bar: coins → $ millions
const avg = ta.sma(math.abs(delta), 50) // a typical size of the delta
plot(delta, { title: 'Delta', style: 'columns', color: delta >= 0 ? color.teal : color.red })
plot(avg, { title: 'Typical |delta|', color: color.gray })
plot(-avg, { title: 'Typical |delta| (sell side)', color: color.gray })
Other timeframes
security(timeframe, () => expression) computes the expression on another timeframe of the same coin and returns it as a series on your chart's bars.

- No repainting by default.
- Compute inside the arrow:
security('1D', () => ta.atr(14)), not() => atr(NV2505); no Wintrading data or your functions in it. - Timeframes:
'1s''5s''15s''1m''5m''15m''30m''1h''2h''4h''1D''1W''1M', aninput.timeframe, Pine's'60'; the chart's coin only. { lookahead: true }— only for what is known at the open:open,high[1].- Braces need
return:() => { const e = ta.ema(close, 20); return close > e }.
//@navy 2.0
overlay('Higher-timeframe EMA')
const tf = input.timeframe('Timeframe', '4h')
const len = input.int('Length', 50, { min: 1 })
// Computed on the bars of tf; on your chart: the value of its last closed bar
const htfEma = security(tf, () => ta.ema(close, len))
const htfUp = security(tf, () => close > ta.ema(close, len))
// Yesterday's high is final at today's open: lookahead does not peek ahead here
const prevDayHigh = security('1D', () => high[1], { lookahead: true })
plot(htfEma, { title: 'EMA (higher timeframe)', color: htfUp ? color.teal : color.red, width: 2, style: 'step' })
plot(prevDayHigh, { title: 'Previous day high', color: color.gray, style: 'step' })More — seconds, how much history loads: Scripts → Other timeframes.