Flowscript

Triggers and orders

When a strategy acts (when, while and the combinators) and every order it can place on spot, perps, liquidity and protocols.

when and while#

TriggerFiresUse it for
when (cond) { … }On the bar cond turns true, once per crossingEntries and exits
while (cond) { … }At every bar cond holdsActions meant to repeat, like requoting, with a guard that turns false

An if inside a body is read at the bar the trigger fires.

A while that buys can spend everything

while (close > ema(close, 50)) { paper.buy({ quote: 100 }); } buys on every bar the price is above its average. Put entries in when, gate them on !inPosition(), or set pyramiding. The compiler warns (FS0902) when a while buys with no guard.

Combinators#

CombinatorTrue when
crossover(a, b), crossunder(a, b)a crosses above or below b this bar
rising(x), falling(x)x is higher or lower than the bar before
changed(x)x differs from the bar before
once(c)The first bar c is true, ever
sustained(c, n)c has held for n bars
cooldown(hours(6))At least that long since it last let a trigger through
hysteresis(x, on, off)x passed on, and hasn't fallen back under off
every("1d"), daily, duringOn a schedule, or inside a window

Spot orders#

On a pool or a feed:

OrderDoes
paper.buy({ quote })Spends quote (fees included) at the bar's close
paper.buyPct({ pct })Spends pct percent of equity
paper.sell({})Sells the whole position
paper.sellPct({ pct })Sells pct percent of what it holds
paper.buyLimit({ quote, at }), paper.buyStop({ quote, at })Rests a buy that fills at the first later close at or under (limit) or over (stop) at
paper.sellLimit({ at }), paper.sellStop({ at })Rests a sell of the position at at

Read the position with inPosition(), avgEntry(), realizedPnl(), unrealizedPnl() and timeInPosition() (seconds).

Perps orders#

Start the program with uses { bars: "^1", funding: "^1", perp: "^1" }.

OrderDoes
perp.long({ usd }), perp.short({ usd })Opens or adds that USD notional, closing the other side first
perp.close({})Goes flat
perp.bid({ usd, at }), perp.ask({ usd, at })Rests a post-only maker order at at
perp.cancel({})Pulls resting maker orders
maxImpactBps: n on any of the aboveTakes only what rests within n bps of the middle

Read the position with perp.sizeUsd() (signed, 0 when flat), perp.entry() and perp.equity(), and funding with funding.rate(market, interval). Size by equity (perp.equity() * share / 100) so it compounds.

Over several markets#

lp.range({ widthPct }) and lp.close({}) hold liquidity; perp.hedge({ ratio }) and perp.target({ usd }) steer a perp position; funds.toPerp({ usd }) and funds.toWallet({ usd }) move margin. See Hedged and multi-market.

Protocols#

Each protocol is named by its id with the same actions as every protocol of its kind:

KindReadsActions
Lending (aave, spark, compound, kamino)supplyApy, supplied(), borrowed(), healthFactor()supply, withdraw, borrow, repay
Vaults (morpho, euler, yearn, fluid, sky, ethena…)apy, deposited()deposit, withdraw
Fixed rates (pendle)impliedApy, ptPrice, held()buy, sell
Options (derive)index, iv7d, iv30d, value(), delta()buy, sell, close
Best rate (earn)apy("usdc"), deposited(), heldApy()deposit, withdraw, rebalance

See Protocols.

Sizes#

A size is a number or a setting as written. A size computed from other values is refused at save, unless the program publishes it with metric() first, so every step records the value it traded. Use paper.buyPct to size by equity.

When an order can't be done#

An order that can't be done (not enough cash, nothing to sell, a resting bid above the close) reverts: it's skipped, the reason goes in the backtest report and the Log, and the run carries on.