# 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.

> [!WARNING] 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

| 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). These read the paper book of a pool or a feed: on a perp they are `na`, so a `when` gated on them never fires there. On a perp, read `perp.sizeUsd()` instead (below).

## 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](https://docs.ftw.trade/strategies/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](https://docs.ftw.trade/protocols/overview).

## 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.
