Wintrading

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

Navy in one picture: series, state, na, logic, inputs, data, timeframes, outputs — a tile each
The topic in one picture.

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
close is the bar being computed, close[1] the one before; before the first bar — na.
  • [k] works on built-in series, Wintrading's per-bar data (liq.long[1]), top-level variables (ema[1]), expressions ((high - low)[1]); k may be a variable: close[i].
  • No history of a call — const hh = ta.highest(high, 20), then hh[1], not ta.highest(high, 20)[1] (NV2403) — nor of a block variable, a string, a color, an object.
  • A variable k reaches 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.

The same counter declared with let, var and varip over three closed bars and three updates of the live bar: let stays 1, var counts 1 2 3 then stays 4, varip counts on to 6
let starts over on every bar, var keeps its value, varip also counts the updates.
DeclarationWhat it isTo the next bar
const x = …Computed on every bar; cannot be reassignedno
let x = …Computed on every bar; may change during the barno
var x = …Set on the first bar, then keeps its value; rolled back before every update of the live baryes
varip x = …Like var, never rolled back: counts every updateyes, updates too
  • =, +=, -=, *=, /=, %=; ++/-- only in a for header.
  • var total = 0 is 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 = na work.
//@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.

TypeValuesCan be na
floatA number (64-bit)yes
intA whole numberyes
booltrue, falsenever
stringText: 'abc' or "abc"no
colorcolor.red, '#ff9800', color.rgb(…)no
line, box, labelA drawing objectyes: none yet
array<T>, map<K, V>A list, a dictionaryyes: none yet
A table over five bars: close, a 3-bar average that is na on the first two bars, the distance that is na there too, the comparison that is false there, and nz of the distance that gives 0
na passes through arithmetic; a comparison with it is false; nz() puts a value in.
  • int → float freely; back only with int(x), math.floor, math.ceil. 7 / 2 is 3.5.
  • Conditions are bool: if (!na(x)), not if (x) for a number (NV2101).
  • na: test na(x) (x == na is 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 like int(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 { … } and cond ? a : b. No switch — use else if.
  • for (let i = 0; i < n; i++) { … } (also i--, 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).
Ten bars: a ta.ema at the top level sees all of them; the same call inside if (close > open) sees only the green bars, so its average is different — the editor warns NV3001
ta.* inside if sees only the bars where the branch ran — compute at the top level.
//@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 { … }; one return value.
  • Each call has its own state: the two zscore calls share no ta.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 var it lives from bar to bar; in let/const it 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).

Three input lines of a script in the app — Fast EMA 9, Slow EMA 21, Volume multiplier 1.5 — and the real settings window they make, numbered to match; the rules for groups, untitled inputs and tooltips
Three input.* lines and the settings window they make.
InputIn the settings windowGives
input.int(title, 14, { min, max, step, options })A number field; with options a drop-downint
input.float(title, 1.5, { min, max, step, options })A number field; with options a drop-downfloat
input.bool(title, true)A checkboxbool
input.string(title, 'EMA', { options })A text field; with options a drop-downstring
input.color(title, color.orange)A color pickercolor
input.source(title, close)A drop-down of price series: close, hl2, ohlc4 …a series
input.timeframe(title, '4h')A drop-down of timeframesstring
input.enum(title, Mode.Fast)A drop-down of the enum's membersthe 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, a security() 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

A map of everything a script can read: candles, ta.* functions, vol.*, liq.*, oi.*, fp.*, book.*, walls.* and security(), each with a small picture and where it has data
Every kind of data, and where it is available.
NameWhat it isType
open, high, low, closePrices of the bar; close is the last price on the live barfloat
volumeVolume of the barfloat
hl2, hlc3, ohlc4, hlcc4(high + low) / 2 and the other averaged pricesfloat
timeThe bar's open time, ms since 1970, UTCfloat
bar_indexThe bar's number: 0 on the first loaded barint
barstate.isfirst, .islast, .ishistory, .isrealtime, .isnew, .isconfirmedThe first bar; the live one; a closed bar; a bar updated live; its first run; its last runbool
syminfo.ticker, syminfo.ticksize (syminfo.mintick)The symbol (BTCUSDT) and its tick sizefixed
timeframe.msThe length of one bar, msint
  • vol, liq, oi, fp, book — per bar with history; walls — a live list: read it in if (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 })
The example on a BTCUSDT 1m chart: volume delta in millions of dollars as teal and red columns with a gray band of its typical size
The example: volume delta in $M with its typical size (the delta here is sample data).

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.

A 15m chart under three 1h bars: by default each 15m bar gets the value of the 1h bar that had closed by its end; with lookahead it gets the 1h bar it is inside, even the forming one
By default: the 1h bar closed by the end of each bar; with lookahead: the 1h bar it is inside.
  • 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', an input.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.