Flowscript

Values, series and na

The rules of Flowscript on one page: values and series, na, expressions, functions, folds, enums and match, phases and libraries.

Values#

KindExample
Number1, 0.5, 1e-3
Conditiontrue, false or na
Text"ETH"
Record{ usd: 100, side: "long" }
List[1, 2, 3]

Everything is a series#

Every const is a series: one value per bar. close is today's close; close[1] is the bar before; close[5] five bars back. Arithmetic works bar by bar, so (high + low) / 2 is a series of midpoints.

Durations are seconds: seconds(30), minutes(15), hours(6).

na: not known yet#

na means a value isn't known yet: an indicator still warming up, a division by zero, a bar with no data.

  • It spreads: na + 1 is na, na > 3 is na, and !na is na.
  • A trigger never fires on na.
  • Test for it with isNa(x); replace it with nz(x, 0).

So write the condition you mean: close > ema(close, 200) is na (not false) for the first 200 bars.

Expressions#

  • Arithmetic + - * / %, comparisons < <= > >= == != (they don't chain: write 1 < x && x < 3), logic && || !.
  • c ? a : b, and if (c) a else b.
  • Math: abs, min, max, clamp, ln, exp, sqrt, pow, floor, ceil, round, sign.
  • Time: bars.time(market, interval) gives each bar's start; hour, minute, dayofweek read it in UTC, and nyTime, londonTime, tokyoTime and others give a city's clock with daylight saving. inWeekdaySession(nyTime(t), 930, 1600) is the US stock session.

Functions and folds#

fn mid(a: Series<number>, b: Series<number>): Series<number> { return (a + b) / 2; }
const m = mid(high, low);
const doubled = map(xs, (x) => x * 2);

There are no loops and no variables to change. State that carries from bar to bar is a fold: a seed and a step from the last state and the new value.

const streak = fold(close, 0, (st, c) => isNa(c) ? st : (c > c[1] ? st + 1 : 0));

Guard every fold step with isNa(x) ? st : …: one missing bar would otherwise turn the state na forever.

Enums and match#

Flowscript
enum Regime { hot, quiet, normal }
const close = bars.close(market, interval);
const vol = stddev(change(ln(close)), 24);
const regime = vol > 0.02 ? Regime.hot : (vol < 0.005 ? Regime.quiet : Regime.normal);
metric("size", match (regime) { hot => 0, quiet => 1, _ => 0.5 });

A match must cover every variant or end in _. A swap's side and a source's cases are enums too.

Phases#

phases Trade { phase Flat { … } phase Long { … } } is a state machine: inside each phase, when … => Phase moves to another, at most once a bar. It's the clearest way to write "wait for a setup, then enter, then manage the position".

Higher timeframes#

On a 15-minute run, bars.close(market, "4h") is the last closed four-hour bar's close, held until the next one closes. It never repaints. higher("4h", close, c => ema(c, 20)) runs a calculation on the four-hour bars themselves.

Libraries#

Keep functions you reuse in a library (save_library) and import "mylib"; after the uses line. A strategy keeps the library version it was saved with until you save it again.

Exact integers#

Numbers are 64-bit floats. For token amounts that need every digit, the u256 functions carry exact 256-bit integers (u256Add, u256Mul, u256Units(1.1, 18) and more). Overflow and division by zero are na, and an order with an na argument is refused before it's signed.