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#
| Trigger | Fires | Use it for |
|---|---|---|
when (cond) { … } | On the bar cond turns true, once per crossing | Entries and exits |
while (cond) { … } | At every bar cond holds | Actions meant to repeat, like requoting, with a guard that turns false |
An if inside a body is read at the bar the trigger fires.
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#
| Combinator | True 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, during | On a schedule, or inside a window |
Spot orders#
On a pool or a feed:
| Order | Does |
|---|---|
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" }.
| Order | Does |
|---|---|
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 above | Takes 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:
| Kind | Reads | Actions |
|---|---|---|
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.