<!-- Generated by `npm run skills` in apps/web from apps/web/lib/strategy-guide.ts (what get_strategy_guide returns). Edit that, not this file. -->

# Strategy guide

The guide `get_strategy_guide` hands an agent: Flowscript as ftw.trade runs a strategy. Every example here saves and backtests in the app's tests, and every pitfall's fix saves.

A strategy is Flowscript run once per closed bar over one market and interval. It reads that market's bars, publishes metrics, and places orders from when and while triggers. Write it, save_strategy (problems come back by line), backtest_strategy, read every order's outcome and reason, revise, then run_strategy. Strategies manage real money once deployed with an account, so prefer one entry at a time, an exit for every entry, and a backtest whose orders all filled.

## Shape

- Above your first line the server adds `const market = "<series>";`, `const interval = "<5s|15s|1m|5m|15m|1h|4h|1d>";` (5s and 15s on a pool, built from its swaps, or on a perp, built from its recorded order book: each bar the middles of the snapshots in it, and a running strategy on them has its book recorded every second, so it steps on each push of the book) and one const per parameter, set to its argument. Read bars with bars.close(market, interval) and use the parameters by name.
- Longer timeframes of the run's own bars (Pine's request.security, never repainting): bars.close(market, "4h") on a 15m run is the last closed four-hour bar's close, known on the 15m bar that closes it and held until the next closes; bars.open/high/low/volume(market, "4h") are that whole bar's, bars.time(market, "4h") its start. higher("4h", close, c => ema(c, 20)) runs its body on the four-hour bars themselves (an ema of 20 four-hour closes; ema(bars.close(market, "4h"), 20) would be an ema of 15m bars holding one value). A timeframe is a whole number of the run's bars, up to 30 days ("4h" on 15m, "1d" on 1h); another market's bars are not readable.
- Outputs (lib/runner-outputs.ts): a strategy can write streams others subscribe to, like any source. Declare each in its code, `// @output ema-cross.entry { price: number, side: text, strong?: boolean }` (after any `uses` line; kinds number, text, boolean, address, pool, hash, url, json), and write it with `outputs.entry({ price: close, side: "buy" })`, the stream's last part with - as _, inside a when or while (it is refused at the top level), each computed field published with metric() first, as a computed order argument is (const z = …; metric("z", z); then z). Every event also carries market and bar (the bar's close, unix seconds). A live run writes them as its bars close, before it sends that bar's orders; a backtest writes nothing. `// @output ema-cross.intent` and `// @output ema-cross.result`, with no fields, are written for each order a run sends: intent (side, token, size, unit, wallets) just before it goes out, result (side, token, ok, filled, eth, detail) once it lands, so a workflow or strategy can act on an order while its transaction is in flight. One writer per stream, and a strategy never reads its own.
- Polling a source on demand: `poll.source({ name: "pendle-rates" })` asks one of your sources to run now, a Flowscript source polling a URL (from: { poll: "https://…", read }, every left out to poll only when asked) or a script source, named by the stream it publishes. Put it in a when or a while: on a live bar where it holds the source runs once (at most once a second, skipped while a run of it is going), and what it publishes reaches the strategy's tape of it on the next bar, so a strategy reads a slow API only when it is about to trade, e.g. when (crossover(fast, slow)) { poll.source({ name: "pendle-rates" }); }. A backtest polls nothing.
- A source as the market: series "source:<name>" backtests and runs a strategy on one of your sources, its events made into bars, as a pool, a feed or a perp is (list_backtest_sources lists them). A source whose events are bars (open, high, low and close numbers, as a Flowscript source with bars publishes) gives a bar each; any other gives its price field (price, else close, else its one number field, or ?price=<field>) as the first, highest, lowest and last value in each bar, and volume from a volume field (?volume=<field>). ?pool=base:0x… keeps only the events whose field has that value, so a source keyed by pool charts one pool. An event is when it was published, or ?time=<field> (unix s or ms; a bar source's start by default). Bars can be as short as 5s, and a bar with no event holds the last close. Live, each bar is stepped once its events are in, a bar source's a second or two after it closes. bars.usd is refused: a source carries no unit.
- With no `uses` line a strategy gets `uses { bars: "^1", paper: "^1" }`. Write your own as the first line to trade a perp (`uses { bars: "^1", funding: "^1", perp: "^1" }`) or more than one market.
- params: up to 20 of { name, label?, kind: number|text|bool, default, min?, max? }. Each becomes a constant, so it can size an order, set an indicator length or pick a threshold. Or declare one in the program itself, where its search space lives too: `const lookback = param(7, { min: 3, max: 30, step: 1 });`, `const weight = param("equal", { options: ["equal", "inverseVol"] });`. Saving makes each declaration a param (a label or help given for it is kept); a run binds its value in place, so it still sizes a window; and optimize_strategy searches the declared steps and options when no space is named. min ≤ default ≤ max; step needs min and max (up to 1000 values); options holds the default and can't be mixed with min/max/step.
- How it is run can live in the program too (D1541), one comment line each: `// @backtest { series: "perp:hyperliquid:BTC", interval: "5m", days: 90, cash: 1000, leverage: 2, slippageBps: 1 }` (backtest_strategy's settings, days for the last N days, or from/to as unix seconds or "YYYY-MM-DD"), `// @optimize { objective: "sharpe", method: "genetic", trials: 200, holdout: 30, walkForward: true, minTrades: 10 }` (run_optimization's, the space being its param() ranges) and `// @deploy { interval: "5m", cash: 500 }` (run_strategy's; it also takes @backtest's market and fills). Then backtest_strategy { id }, run_optimization { id } and run_strategy { id } need nothing else, and what a call gives wins. @deploy never names an account: trading wallets is chosen when a run starts.
- The Flowscript version can be its first line: `flowscript "0.3";`. Version 0.3 adds checked signed i64/long and uint256 math, exact decimal unit conversion and full 512-bit multiply-divide with down/up rounding. A saved strategy keeps its recorded language version; explicit 0.1/0.2 programs keep their unsigned numeric-input policy. New default artifacts pin 0.3, and an unsupported version refuses on that line.
- Exact amounts: import "std/math"; const amount = parseUnits("1.000000000000000001", 18); const minimum = basisPoints(amount, 9970); use u256Cmp for ordering. Integer results are decimal text, so ordinary + concatenates and ordinary text ordering is lexical. u256/i64 numeric integer arguments must be safe integers; use quoted exact text beyond 2^53-1. u256MulDiv/u256MulDivUp keep the full 512-bit product before checking the final uint256 bound. i64Add/Sub/Mul/Div/Mod/Neg/Abs are checked signed 64-bit arithmetic; invalid dynamic inputs and overflow are na. u256Format is exact decimal formatting; Number conversions are approximate display and SafeNumber refuses outside the safe integer range.
- There are no loops, no mutation and no hidden state: every name is bound once with const, a value over time is a series, `x[1]` is the previous bar, and fold(series, seed, (st, x) => next) is the one way to carry state.

## Reads

- bars.open/high/low/close/volume(market, interval), and bars.time(market, interval): when the bar opened, unix seconds, for hour(t), minute(t) and dayofweek(t) in UTC (1 is Sunday, so weekdays are 2 to 6). Another city's clock, daylight saving included: nyTime(t), chicagoTime(t), londonTime(t), frankfurtTime(t), sydneyTime(t), tokyoTime(t), hongKongTime(t), so hour(nyTime(t)) is New York's hour all year. A session is inSession(lt, start, end) with HHMM times (Pine's "0930-1600"; an end before the start runs past midnight), or inWeekdaySession(…) for Monday to Friday: the US stock session is inWeekdaySession(nyTime(t), 930, 1600). sessionStarts(active), sessionOpen(open, active), sessionHigh(high, active) and sessionLow(low, active) give its first bar, open, high and low so far. The session VWAP a chart draws is periodVwap(price, volume, t, 86400), reset at each UTC day's first bar (another period's length in seconds for another anchor), and periodVwapDev(…) its volume-weighted σ, so periodVwap(…) ± 2 * periodVwapDev(…) are the 2σ bands; anchoredVwap(price, volume, anchor) and anchoredVwapDev(…) start from the last bar anchor held instead (a swing low, a news bar), and vwap(price, volume, len) is a rolling window. import "std/vwap"; adds weekStarts(t) and monthStarts(t) anchors, vwapZ(price, vw, dev), vwapDrift(vw, dev, anchor), ridingAbove/ridingBelow(close, vw, dev, len) and failedPushUp/failedPushDown(…). See the starter "VWAP rotation: fade failed pushes to the mean". The New York Stock Exchange's own calendar, holidays and 13:00 closes kept: nyseSession(t) (open at t), nyseTradingDay(t), nyseHoliday(t), nyseEarlyClose(t), nyseMinutesToClose(t) (na outside the session) and nyseClose(t) (the day's close as HHMM, 0 when shut). The day's US market news, rated 1 bearish to 99 bullish each weekday at 15:30 New York, is the shared feed us-news-mood: feed.close("us-news-mood", interval). See the starter "Overnight hold: buy the close, sell the open".
- funding.rate(market, interval) on a perps market: hourly, as a fraction.
- The order book (D1337, D1495), on a perps market or a Polymarket outcome (poly:<token id>): book.bid(market, interval) and book.ask(…) are the best prices, book.bidSize(…) and book.askSize(…) what rests there (base units; shares on Polymarket), book.mid(…) the middle, book.micro(…) the microprice (the middle leaning toward the thinner side: (bid × askSize + ask × bidSize) ÷ (bidSize + askSize)), book.spread(…) the spread in bps of the middle. Levels 2 to 5: book.bid2(…) … book.bid5(…), book.ask2…5, book.bidSize2…5, book.askSize2…5. Depth in USD: book.bidDepth(…) and book.askDepth(…) on the top 20 levels, book.imbalance(…) = (bidDepth − askDepth) ÷ both, +1 all bids; book.bids50(…) and book.asks50(…) within 0.5% of the middle (bids10/asks10 within 0.1%, 25 within 0.25%, 100 within 1%) and book.imbalance50(…) there (imbalance10, 25, 100 likewise). On Polymarket, where a tick is a cent, read levels and bidDepth/askDepth rather than the bps bands. book.age(…) is how many seconds old the snapshot was at the bar's close. Order-flow over time: book.bidDepth(market, interval) - book.bidDepth(market, interval)[1] is what was added to the bids since the last bar; ema(book.imbalance(market, interval), 10) smooths it. build records a book every 15 s (a perps market in Your pools, the market of any running strategy that reads it, or one you start with update_book_recording); each bar reads the newest snapshot before its close no older than the bar (45 s for bars under a minute), and na where there is none: let a condition on it stay false, or wrap it in nz(…, 0). A backtest over bars with nothing recorded is refused, saying so; get_book_history shows what was recorded. Write `uses { bars: "^1", book: "^1", perp: "^1" }` on a perp or `uses { bars: "^1", book: "^1", paper: "^1" }` on Polymarket. Another market's book (D1499) is read by its key written in the program, beside the run's own: book.mid("perp:lighter:ETH", interval), book.bid("poly:<token id>", interval), book.mid("derive:ETH-PERP", interval) (Derive's books are recorded too), or a const holding one (const fair = "derive:ETH-PERP"; book.mid(fair, interval)), so a quote on one venue can lean on another's; it is recorded while a run reads it. A strategy over more than one market (a pool, a feed beside its perp) reads no book yet; a live run holds while its own book is not being recorded. See the starters "Order book imbalance", "Microprice lean", "Polymarket book pressure" and "Depth-capped breakout". To react to the book quickly, run on 5s or 15s bars: the book is then recorded every second and the run steps on each push (no step per book update: a venue pushes several a second, most of them noise); reads are the snapshot at the bar's close, and an order's price is read from the live book when it is sent.
- Liquidations (Hyperliquid perps BTC, ETH, ZEC, SOL and HYPE; lib/liquidations.ts): where real open positions would be liquidated, read from the accounts themselves (Hyperliquid states each position's liquidation price), recorded a minute at a time from 2 Nov 2026. liq.longs1(market, interval), liq.longs2, liq.longs5 and liq.longs10 are the USD of long positions liquidated within 1, 2, 5 and 10% below the price, liq.shorts1…shorts10 the same above; liq.imbalance(…) = (shorts10 − longs10) ÷ both, +1 all shorts. The densest 0.5% cluster each side within 10%: liq.longPx(…) and liq.longUsd(…) below, liq.shortPx(…) and liq.shortUsd(…) above; within 3%: liq.nearLongPx/nearLongUsd and liq.nearShortPx/nearShortUsd (na when that side holds nothing in reach). liq.covered(…) is the USD of positions read (accounts are found as they trade, so it grows), liq.age(…) the row's age in seconds. A "price near a cluster" alert: (liq.nearShortPx(market, interval) - close) / close * 100 < 0.5 && liq.nearShortUsd(market, interval) > 5000000. Write `uses { bars: "^1", liq: "^1", perp: "^1" }`. Each bar reads the newest row before its close no older than the bar (3 minutes for shorter bars), na otherwise; a backtest over bars before recording began is refused. The usual estimated heatmap showed no edge on 12 Hyperliquid perps (2 Nov 2026), so treat these as context or filters until a backtest on the recorded map says otherwise.
- bars.usd(market, interval) (D1244): the close in USD. On a pool quoted in ETH, SOL or BTC it is the close times that coin's USD price when the bar closed, on a stablecoin pool, a USD feed or a perp it is the close. Market cap is bars.usd(market, interval) * supply, with supply a param: when (bars.usd(market, interval) * supply > 1000000) { … }.
- Typed sources (D1233): sources.<name>() is a tape of the account's source <name> (its - and . written _), each event's fields as the source declares them. A number field is a number, a boolean 1 or 0, an address, hash, URL, chain, pool or text field is text, JSON its compact text; an optional field left out is 0 or "". An array field (D1544: "address[]", [{ pool: "pool", usd: "number" }]) is an array: buffer(tape, n) keeps the tape's last n events as an array of records, newest last, so const flows = buffer(sources.top_pools(), 10); const top = size(flows) > 0 ? flows[-1].top : []; reads the newest event's, and map, filter, sortBy, groupBy, sum and the rest work on it (sum(map(top, (f) => f.usd))); inside a tape's map, size(e.top) is one number per event. Pick a field with map(tape, (e) => e.usd), bucket with window(…, "1h") or resample(…, "1h"), reduce with count, sum, mean, first, last or distinct: const calls = sources.kol_calls(); when (count(window(calls, "15m")) >= 3) { paper.buy({ quote: 100 }); }. Each event lands in the bar that contains it. A text field can be compared (e.token == "0x…") but never sizes or names an order. A source that is a sum type (D1236) carries its tag as an enum: map(sources.kol(), (e) => match (e.case) { call => 1, exit => -1, note => 0 }) must name every case or end in _ => …, and a case's fields read 0 or "" on events of the other cases. A strategy over one series only; a running strategy stops if a source it reads is deleted, retyped or gains or loses a case.
- started is the unix second the run started (a deployment's start bar, a backtest's first bar), given to a program that reads it: with const t = bars.time(market, interval), t >= started + minutes * 60 is a delay since the start. The starter "Staged buys, then exit" uses it.
- A pool's swaps (D1239): pool.swaps() is a tape of every swap of the pool the strategy runs on (a run on one of Your pools), each event { side, base, quote, price, trader, tx, block }: side is an enum, buy (the trader took the base token out) or sell, base and quote are the amounts, price is quote per base, trader is the address or "". const s = pool.swaps(); const bought = sum(window(map(s, (e) => match (e.side) { buy => e.quote, sell => 0 }), "15m")); counts what buyers paid in the last 15 minutes. Each swap lands in the bar that holds it.
- The paper book, on a pool or a feed: inPosition(), avgEntry() (quote per unit, fees included), realizedPnl(), unrealizedPnl(), timeInPosition() (seconds). On a perps market these read na (unknown); read the perp's own book instead: perp.sizeUsd() (signed USD notional held, 0 flat), perp.equity() and wallet.cash() run the strategy on that market's book bar by bar, so, with uses { bars: "^1", perp: "^1" } first, when (perp.sizeUsd() == 0 && entry) { perp.long({ usd }); } enters once and when (perp.sizeUsd() > 0 && exit) { perp.close({}); } exits once (Pine's strategy.position_size). All its cash starts in the perp account unless margin says. perp.long closes a short first, and perp.close({}) with nothing open does nothing. perp.entry() is the average entry price of what it holds (0 flat), for a stop in code: when (perp.sizeUsd() > 0 && close < perp.entry() * 0.97) { perp.close({}); }. Such a strategy runs on a project's wallets as any perp strategy does (each wallet sends the order's USD notional), reads sources, and takes stopPct and takePct.
- Over more than one market: pool.*("chain:0x…", interval), perp.close("venue:SYMBOL", interval), perp.funding(…), feed.close("feed-name", interval), lp.inRange(), lp.widthPct(), lp.baseUsd(), lp.value(), lp.fees(), perp.sizeUsd(), perp.entry(), perp.equity(), wallet.cash(). Reading a pool, a feed, another perp or lp.*, funds.* or a protocol makes it a strategy over more than one market, which reads each by key; perp.sizeUsd(), perp.entry(), perp.equity() and wallet.cash() alone on a perps market keep bars.close(market, interval) and run as above. See list_strategies and the starter "Hedged range, width by session and volatility".
- Protocols, directly (D1342), each named by its id. Options (D1356): derive (Derive, D1356): its options on ETH, BTC, SOL, HYPE, XRP, ADA, ZEC, XAUT (gold), LIT, VVV, PUMP and CC: the index and the at-the-money volatility 7 and 30 days out; calls and puts bought and sold, settled at expiry; on a live project each wallet holds the same options on Derive from a subaccount of its own; underlyings eth, btc, sol, hype, xrp, ada, zec, xaut, lit, vvv, pump, cc. <id>.index("eth", interval) is the venue's index for the underlying, <id>.iv7d(…) and <id>.iv30d(…) its at-the-money volatility 7 and 30 days out, percent; reading one names the underlying its actions trade. <id>.value() is the options held at the model's value (a sale counts against it), <id>.delta() their delta in USD of the underlying, <id>.positions() how many are open. Swap venues (D1501): jupiter (Jupiter): its aggregator routes swaps across every Solana DEX; tokens SOL, JUP, JLP, JitoSOL, mSOL, cbBTC, ETH, JTO, PYTH, RAY, BONK and WIF against USDC, with limit orders (Trigger) and DCAs (Recurring). jupiter.price("solana:sol", interval) is a token's USD price there and jupiter.impact(…) what a $10,000 USDC buy of it costs over that price on its route, percent (a smaller buy costs that share of it); reading one names the token its swaps and orders trade, one token per strategy, beside its other books. jupiter.held() is the token held in USD (what its limit sells hold included; heldUsd() says the same, the name a protocol on the pattern gives it), jupiter.units() the tokens free to sell, jupiter.avgPrice() what one cost, jupiter.locked() the cash its buy orders and DCAs hold, jupiter.openOrders() how many rest. Tokens: solana:sol, solana:jup, solana:jlp, solana:jitosol, solana:msol, solana:cbbtc, solana:eth, solana:jto, solana:pyth, solana:ray, solana:bonk, solana:wif. Read from its contracts (D1522): aave (Aave): Lending and borrowing on Aave v3's pools on Ethereum, Base and Arbitrum. Deployments ethereum, ethereum-prime, ethereum-etherfi, base, arbitrum; a market is any its deployment lists ("arbitrum:usdc"). Market reads, a series each: aave.supplyApy("arbitrum:token", interval), aave.borrowApy("arbitrum:token", interval), aave.utilization("arbitrum:token", interval), aave.available("arbitrum:token", interval), aave.totalSupplied("arbitrum:token", interval), aave.totalBorrowed("arbitrum:token", interval), aave.supplyRoom("arbitrum:token", interval), aave.borrowRoom("arbitrum:token", interval), aave.price("arbitrum:token", interval), aave.ltv("arbitrum:token", interval), aave.threshold("arbitrum:token", interval). Its account, at the bar: aave.supplied("arbitrum:token"), aave.borrowed("arbitrum:token"), aave.healthFactor("deployment"), aave.collateralUsd("deployment"), aave.debtUsd("deployment"), aave.borrowableUsd("deployment"), aave.currentLtv("deployment"), aave.liquidationLtv("deployment"), aave.eMode("deployment"), aave.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): spark (Spark): Lending and borrowing on SparkLend, an Aave v3 pool on Ethereum. Deployments ethereum; a market is any its deployment lists ("ethereum:usdc"). Market reads, a series each: spark.supplyApy("ethereum:token", interval), spark.borrowApy("ethereum:token", interval), spark.utilization("ethereum:token", interval), spark.available("ethereum:token", interval), spark.totalSupplied("ethereum:token", interval), spark.totalBorrowed("ethereum:token", interval), spark.supplyRoom("ethereum:token", interval), spark.borrowRoom("ethereum:token", interval), spark.price("ethereum:token", interval), spark.ltv("ethereum:token", interval), spark.threshold("ethereum:token", interval). Its account, at the bar: spark.supplied("ethereum:token"), spark.borrowed("ethereum:token"), spark.healthFactor("deployment"), spark.collateralUsd("deployment"), spark.debtUsd("deployment"), spark.borrowableUsd("deployment"), spark.currentLtv("deployment"), spark.liquidationLtv("deployment"), spark.eMode("deployment"), spark.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): compound (Compound): Lending and borrowing on Compound III's Comets on Ethereum, Base and Arbitrum: each lends one base asset against its collaterals. Deployments ethereum, ethereum-weth, ethereum-usdt, ethereum-wsteth, ethereum-usds, ethereum-wbtc, base, base-usdbc, base-weth, base-aero, base-usds, arbitrum, arbitrum-usdce, arbitrum-weth, arbitrum-usdt; a market is any its deployment lists ("arbitrum:usdc"). Market reads, a series each: compound.supplyApy("arbitrum-usdt:token", interval), compound.borrowApy("arbitrum-usdt:token", interval), compound.utilization("arbitrum-usdt:token", interval), compound.available("arbitrum-usdt:token", interval), compound.totalSupplied("arbitrum-usdt:token", interval), compound.totalBorrowed("arbitrum-usdt:token", interval), compound.supplyRoom("arbitrum-usdt:token", interval), compound.borrowRoom("arbitrum-usdt:token", interval), compound.price("arbitrum-usdt:token", interval), compound.ltv("arbitrum-usdt:token", interval), compound.threshold("arbitrum-usdt:token", interval). Its account, at the bar: compound.supplied("arbitrum-usdt:token"), compound.borrowed("arbitrum-usdt:token"), compound.healthFactor("deployment"), compound.collateralUsd("deployment"), compound.debtUsd("deployment"), compound.borrowableUsd("deployment"), compound.currentLtv("deployment"), compound.liquidationLtv("deployment"), compound.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): morpho (Morpho): MetaMorpho vaults over Morpho Blue on Ethereum, Base and Arbitrum: a curator lends each vault's deposits (USDC, WETH) across the Blue markets it picks, up to each market's cap. Deployments ethereum, base, arbitrum; a market is any its deployment lists ("arbitrum:hyperusdca"). Market reads, a series each: morpho.apy("arbitrum:vault", interval), morpho.sharePrice("arbitrum:vault", interval), morpho.totalAssets("arbitrum:vault", interval), morpho.available("arbitrum:vault", interval), morpho.depositRoom("arbitrum:vault", interval). Its account, at the bar: morpho.deposited("arbitrum:vault"), morpho.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): euler (Euler): Euler's EVK lending vaults on Ethereum, Base and Arbitrum: each lends one token (USDC) to the borrowers its governor allows, at the rate its rate model sets from how much is borrowed. Deployments ethereum, base, arbitrum; a market is any its deployment lists ("arbitrum:k3theo"). Market reads, a series each: euler.apy("arbitrum:vault", interval), euler.sharePrice("arbitrum:vault", interval), euler.totalAssets("arbitrum:vault", interval), euler.available("arbitrum:vault", interval), euler.depositRoom("arbitrum:vault", interval). Its account, at the bar: euler.deposited("arbitrum:vault"), euler.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): yearn (Yearn): Yearn's v3 vaults on Ethereum, Base and Arbitrum, as Yearn's registry endorses them: each lends one token across a queue of strategies, and its share price grows as the profit each report locks frees by the second. Deployments ethereum, base, arbitrum; a market is any its deployment lists ("base:tyd"). Market reads, a series each: yearn.apy("arbitrum:vault", interval), yearn.sharePrice("arbitrum:vault", interval), yearn.totalAssets("arbitrum:vault", interval), yearn.available("arbitrum:vault", interval), yearn.depositRoom("arbitrum:vault", interval). Its account, at the bar: yearn.deposited("arbitrum:vault"), yearn.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): fluid (Fluid): Fluid's lending fTokens on Ethereum, Base and Arbitrum, as its LendingFactory made them: each lends one token into Fluid's Liquidity layer, and its share price grows with Liquidity's supply exchange price, by the second. Deployments ethereum, base, arbitrum; a market is any its deployment lists ("arbitrum:fusdc"). Market reads, a series each: fluid.apy("arbitrum:vault", interval), fluid.sharePrice("arbitrum:vault", interval), fluid.totalAssets("arbitrum:vault", interval), fluid.available("arbitrum:vault", interval), fluid.depositRoom("arbitrum:vault", interval). Its account, at the bar: fluid.deposited("arbitrum:vault"), fluid.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): gearbox (Gearbox): Gearbox's lending pools on Ethereum, as its curators' registers list them: each lends one token to Gearbox's leveraged credit accounts, and its share price grows with the base interest and quota revenue they pay, by the second. Deployments ethereum; a market is any its deployment lists ("ethereum:tulipa-resolv-usdc"). Market reads, a series each: gearbox.apy("ethereum:vault", interval), gearbox.sharePrice("ethereum:vault", interval), gearbox.totalAssets("ethereum:vault", interval), gearbox.available("ethereum:vault", interval), gearbox.depositRoom("ethereum:vault", interval), gearbox.utilization("ethereum:vault", interval). Its account, at the bar: gearbox.deposited("ethereum:vault"), gearbox.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): spark_savings (Spark Savings): Spark's savings vaults (spUSDC, spUSDT and spPYUSD on Ethereum, spUSDG on Robinhood Chain): each pays its vault savings rate on a dollar, compounded by the second, while Spark lends what it takes from the vault elsewhere. Deployments ethereum, robinhood; a market is any its deployment lists ("ethereum:spusdc"). Market reads, a series each: spark_savings.apy("robinhood:vault", interval), spark_savings.sharePrice("robinhood:vault", interval), spark_savings.totalAssets("robinhood:vault", interval), spark_savings.available("robinhood:vault", interval), spark_savings.depositRoom("robinhood:vault", interval). Its account, at the bar: spark_savings.deposited("robinhood:vault"), spark_savings.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): sky (Sky): The Sky Savings Rate through sUSDS on Ethereum, reached with USDC through Sky's PSM: USDS saved grows at the rate governance sets, compounded by the second. Deployments ethereum; a market is any its deployment lists ("ethereum:susds"). Market reads, a series each: sky.apy("ethereum:vault", interval), sky.sharePrice("ethereum:vault", interval), sky.totalAssets("ethereum:vault", interval), sky.available("ethereum:vault", interval), sky.depositRoom("ethereum:vault", interval). Its account, at the bar: sky.deposited("ethereum:vault"), sky.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): ethena (Ethena): Ethena's staked USDe (sUSDe) on Ethereum, reached with USDC through Curve's USDe/USDC pool: staked USDe grows as Ethena's rewards vest over each eight hours, and leaving waits out sUSDe's cooldown. Deployments ethereum; a market is any its deployment lists ("ethereum:susde"). Market reads, a series each: ethena.apy("ethereum:vault", interval), ethena.sharePrice("ethereum:vault", interval), ethena.totalAssets("ethereum:vault", interval), ethena.cooldownDays("ethereum:vault", interval), ethena.usdePrice("ethereum:vault", interval), ethena.poolUsdc("ethereum:vault", interval), ethena.poolUsde("ethereum:vault", interval). Its account, at the bar: ethena.deposited("ethereum:vault"), ethena.cooling("ethereum:vault"), ethena.claimable("ethereum:vault"), ethena.settlesAt("ethereum:vault"), ethena.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): maple (Maple): Maple's Syrup pools on Ethereum (syrupUSDC, syrupUSDT, syrupUSDG): pools that lend to institutional borrowers on open-term loans, whose shares grow as the loans' interest accrues. Bought with the pool's asset on Fluid's DEX, left through Maple's withdrawal queue. Deployments ethereum; a market is any its deployment lists ("ethereum:syrupusdc"). Market reads, a series each: maple.apy("ethereum:vault", interval), maple.sharePrice("ethereum:vault", interval), maple.totalAssets("ethereum:vault", interval), maple.cash("ethereum:vault", interval), maple.depositRoom("ethereum:vault", interval), maple.queuedShares("ethereum:vault", interval), maple.unrealizedLosses("ethereum:vault", interval), maple.dexPrice("ethereum:vault", interval). Its account, at the bar: maple.deposited("ethereum:vault"), maple.queued("ethereum:vault"), maple.claimable("ethereum:vault"), maple.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): uniswap (Uniswap): Swaps through Uniswap's v2 pairs, v3 pools and v4 pools on Ethereum, Base, Arbitrum, Optimism, Unichain and Robinhood Chain, computed with Uniswap's own math to the wei and sent through its Universal Router, one pool or the best route across up to three, and liquidity in v2 pairs, v3 pools and hookless v4 pools put in, taken out and its fees collected, with what it holds, earns and has made. Deployments ethereum, base, arbitrum, robinhood, optimism, unichain; a market is any its deployment lists ("base:weth-usdc-v3-500"). Market reads, a series each: uniswap.price("unichain:token", interval), uniswap.ratio("unichain:token", interval), uniswap.fee("unichain:token", interval), uniswap.depthDown("unichain:token", interval), uniswap.depthUp("unichain:token", interval), uniswap.liquidityUsd("unichain:token", interval). Its account, at the bar: uniswap.held("unichain:token"), uniswap.heldUsd("unichain:token"), uniswap.lpCount("deployment"), uniswap.lpUsd("deployment"), uniswap.lpFeesUsd("deployment"), uniswap.lpInRange("deployment"), uniswap.lpDrift("deployment"), uniswap.lpPnlUsd("deployment"), uniswap.lpIlUsd("deployment"), uniswap.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): kyberswap (KyberSwap): Swaps routed by KyberSwap's aggregator across every DEX it indexes on Ethereum, Base, Arbitrum and Robinhood Chain: quoted by its API at the head for exactly what is sold, and sent through its router only after the call its API builds is checked to sell exactly that, pay only the wallet, take no fee and return at least what the slippage allows. Deployments ethereum, base, arbitrum, robinhood; a market is any its deployment lists ("base:weth"). Market reads, a series each: kyberswap.price("robinhood:token", interval), kyberswap.spread("robinhood:token", interval). Its account, at the bar: kyberswap.held("robinhood:token"), kyberswap.heldUsd("robinhood:token"), kyberswap.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): cow (CoW Swap): Swaps settled by CoW Protocol's solvers in batch auctions on Ethereum, Base and Arbitrum: a sell order quoted by CoW's API at the head, built and hashed by the crate as CoW's settlement contract hashes it (selling exactly what was asked, paying only the wallet, no fee in the order, at least the quote less the slippage), signed by the wallet, and settled by a solver or expired with nothing spent. The wallet sends only an approval of what it sells to CoW's vault relayer. Deployments ethereum, base, arbitrum; a market is any its deployment lists ("arbitrum:arb"). Market reads, a series each: cow.price("arbitrum:token", interval), cow.spread("arbitrum:token", interval). Its account, at the bar: cow.held("arbitrum:token"), cow.heldUsd("arbitrum:token"), cow.open("arbitrum:token"), cow.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): aerodrome (Aerodrome): Swaps through Aerodrome's and Aero's pools on Base, computed with each pool's own math to the wei: its classic pools, volatile (x·y) and stable (x³y + xy³), through Aerodrome's Router, and its concentrated (Slipstream) pools, Uniswap v3's math, through the Slipstream router of the pool's factory, an exact amount in or out. Liquidity put in and taken out (a classic pool's LP token, or a concentrated pool's range as an NFT), and the trading fees it earns claimed, with what it holds, earns and has made. An Aero pool's range staked in its gauge for AERO, taken out and its rewards claimed, with each gauge's reward rate and what a range would earn staked. Deployments base, robinhood, ethereum; a market is any its deployment lists ("ethereum:aero-cl10-wbtc-usdc"). Market reads, a series each: aerodrome.price("ethereum:token", interval), aerodrome.ratio("ethereum:token", interval), aerodrome.fee("ethereum:token", interval), aerodrome.depthDown("ethereum:token", interval), aerodrome.depthUp("ethereum:token", interval), aerodrome.liquidityUsd("ethereum:token", interval), aerodrome.feeGrowth("ethereum:token", interval), aerodrome.rewardRate("ethereum:token", interval), aerodrome.rewardRateUsd("ethereum:token", interval), aerodrome.rewardApr("ethereum:token", interval), aerodrome.stakedShare("ethereum:token", interval). Its account, at the bar: aerodrome.held("ethereum:token"), aerodrome.heldUsd("deployment"), aerodrome.lpShare("ethereum:token"), aerodrome.lpCount("deployment"), aerodrome.lpInRange("deployment"), aerodrome.lpDrift("deployment"), aerodrome.lpUsd("deployment"), aerodrome.lpFeesUsd("deployment"), aerodrome.lpPnlUsd("deployment"), aerodrome.lpIlUsd("deployment"), aerodrome.stakedCount("deployment"), aerodrome.stakedApr("deployment"), aerodrome.rewardsEarned("deployment"), aerodrome.rewardsEarnedUsd("deployment"), aerodrome.claimableUsd("deployment"), aerodrome.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): pendle (Pendle): Pendle's fixed rates: a principal token (PT) is worth one unit of its market's asset at expiry and trades below it before, so holding one locks the market's implied APY. Every market Pendle's factories made on Ethereum, Arbitrum, Base and Robinhood Chain, by address, each family's maturities as a roll, and the best roll: the highest fixed rate any market whose SY takes USDC pays, new markets included as they list. Deployments ethereum, arbitrum, base, robinhood; a market is any its deployment lists ("ethereum:roll:siusd"). Market reads, a series each: pendle.impliedApy("ethereum:0x…", interval), pendle.ptPrice("ethereum:0x…", interval), pendle.ytPrice("ethereum:0x…", interval), pendle.daysLeft("ethereum:0x…", interval), pendle.liquidity("ethereum:0x…", interval), pendle.swapFee("ethereum:0x…", interval), pendle.takesUsdc("ethereum:0x…", interval), pendle.syRate("ethereum:0x…", interval). Its account, at the bar: pendle.held("ethereum:0x…"), pendle.heldApy("ethereum:0x…"), pendle.heldDaysLeft("ethereum:0x…"), pendle.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): marginfi (marginfi): Lending and borrowing on marginfi's main group on Solana: every bank it lists, priced by the bank's own oracle. Deployments solana; a market is any its deployment lists ("solana:usdc"). Market reads, a series each: marginfi.supplyApy("solana:token", interval), marginfi.borrowApy("solana:token", interval), marginfi.utilization("solana:token", interval), marginfi.available("solana:token", interval), marginfi.price("solana:token", interval), marginfi.ltv("solana:token", interval), marginfi.threshold("solana:token", interval). Its account, at the bar: marginfi.supplied("solana:token"), marginfi.borrowed("solana:token"), marginfi.healthFactor("deployment"), marginfi.collateralUsd("deployment"), marginfi.debtUsd("deployment"), marginfi.borrowableUsd("deployment"), marginfi.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): kamino (Kamino): Lending and borrowing on Kamino Lend's markets on Solana (primary, JLP, Jito, Maple, Ethena, altcoins): every reserve each lists, priced by its Scope oracle, with its elevation groups. Deployments solana, solana-jlp, solana-jito, solana-maple, solana-ethena, solana-altcoins; a market is any its deployment lists ("solana-altcoins:usdc"). Market reads, a series each: kamino.supplyApy("solana-altcoins:token", interval), kamino.borrowApy("solana-altcoins:token", interval), kamino.utilization("solana-altcoins:token", interval), kamino.available("solana-altcoins:token", interval), kamino.price("solana-altcoins:token", interval), kamino.ltv("solana-altcoins:token", interval), kamino.threshold("solana-altcoins:token", interval), kamino.borrowFactor("solana-altcoins:token", interval). Its account, at the bar: kamino.supplied("solana-altcoins:token"), kamino.borrowed("solana-altcoins:token"), kamino.healthFactor("deployment"), kamino.collateralUsd("deployment"), kamino.debtUsd("deployment"), kamino.borrowableUsd("deployment"), kamino.currentLtv("deployment"), kamino.liquidationLtv("deployment"), kamino.eMode("deployment"), kamino.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): kamino_earn (Kamino Earn): Kamino's curated vaults on Solana: each lends one token across Kamino Lend reserves under its curator, and its holders earn the reserves' interest less its fees. Deployments solana; a market is any its deployment lists ("solana:allezusdc"). Market reads, a series each: kamino_earn.apy("solana:vault", interval), kamino_earn.sharePrice("solana:vault", interval), kamino_earn.totalAssets("solana:vault", interval), kamino_earn.available("solana:vault", interval), kamino_earn.depositRoom("solana:vault", interval). Its account, at the bar: kamino_earn.deposited("solana:vault"), kamino_earn.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): jupiter_lend (Jupiter Lend): Jupiter Lend Earn on Solana: jlTokens lend one token into Jupiter Lend's liquidity layer, and their holders earn what its borrowers pay, less its fee, with any rewards. Deployments solana; a market is any its deployment lists ("solana:jlusdc"). Market reads, a series each: jupiter_lend.apy("solana:vault", interval), jupiter_lend.sharePrice("solana:vault", interval), jupiter_lend.totalAssets("solana:vault", interval), jupiter_lend.available("solana:vault", interval). Its account, at the bar: jupiter_lend.deposited("solana:vault"), jupiter_lend.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): jito (Jito): Jito liquid staking on Solana: JitoSOL is the pool token of Jito's SPL stake pool, which stakes the SOL deposited with it across many validators and grows by what they earn, MEV tips included. A deposit turns the run's USDC into SOL on Raydium's SOL/USDC pool and stakes it; a withdrawal takes SOL out of the pool's reserve (its fee is 0.1%) and sells it back for USDC, unless keepSol. Computed with the stake pool program's own math, held to its answers on mainnet. Deployments solana; a market is any its deployment lists ("solana:jitosol"). Market reads, a series each: jito.apy("solana:vault", interval), jito.solPrice("solana:vault", interval), jito.price("solana:vault", interval), jito.solUsd("solana:vault", interval), jito.totalStaked("solana:vault", interval), jito.depositFee("solana:vault", interval), jito.withdrawalFee("solana:vault", interval). Its account, at the bar: jito.held("solana:vault"), jito.heldSol("solana:vault"), jito.deposited("solana:vault"), jito.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): pons (Pons): Tokens launched on Pons (v2, Robinhood Chain), bought and sold on each one's own bonding curve until it graduates to a Uniswap v4 pool: a constant-product curve on a phantom quote reserve, trading against native ETH (or the ERC-20 the launch named). Every trade pays the launch's base fee and its creator's tax off the quote; a buy in the launch's opening seconds pays a snipe tax as well, halving fourteen times across its window. The buy that reaches graduation is filled only up to the tokens left and refunded the rest. Computed with the curve's own math, ported from Pons's verified source, to the wei. Deployments robinhood; a market is any its deployment lists ("robinhood:0x…"). Market reads, a series each: pons.price("robinhood:token", interval), pons.priceQuote("robinhood:token", interval), pons.marketCap("robinhood:token", interval), pons.raised("robinhood:token", interval), pons.raisedUsd("robinhood:token", interval), pons.progress("robinhood:token", interval), pons.graduated("robinhood:token", interval), pons.fee("robinhood:token", interval), pons.launchTax("robinhood:token", interval), pons.age("robinhood:token", interval). Its account, at the bar: pons.held("robinhood:token"), pons.heldUsd("robinhood:token"), pons.worthQuote("robinhood:token"), pons.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): pumpfun (pump.fun): Coins launched on pump.fun (Solana), bought and sold on each one's own bonding curve until it completes and the coin moves to its PumpSwap pool: a constant-product curve on virtual SOL and token reserves. Every trade pays the protocol's and the creator's fees off the SOL, at the fee program's market-cap tier; the last buy is filled only up to the tokens the curve has left. The run's USDC is turned into SOL (and back) through Raydium's SOL/USDC pool. Computed with the curve's own math, held to the program's answers on mainnet. Deployments solana; a market is any its deployment lists ("solana:<mint>"). Market reads, a series each: pumpfun.price("solana:token", interval), pumpfun.priceQuote("solana:token", interval), pumpfun.marketCap("solana:token", interval), pumpfun.raised("solana:token", interval), pumpfun.raisedUsd("solana:token", interval), pumpfun.progress("solana:token", interval), pumpfun.graduated("solana:token", interval), pumpfun.fee("solana:token", interval), pumpfun.launchTax("solana:token", interval), pumpfun.solUsd("solana:token", interval). Its account, at the bar: pumpfun.held("solana:token"), pumpfun.heldUsd("solana:token"), pumpfun.worthQuote("solana:token"), pumpfun.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): pumpswap (PumpSwap): PumpSwap (Solana): the constant-product pools pump.fun coins trade on once their bonding curve completes, quoted in SOL. Every trade pays the pool, the protocol and the coin's creator their fees off the SOL, at the fee program's market-cap tier. The run's USDC is turned into SOL (and back) through Raydium's SOL/USDC pool. Computed with the pool's own math, held to the program's answers on mainnet. Deployments solana; a market is any its deployment lists ("solana:<mint>"). Market reads, a series each: pumpswap.price("solana:token", interval), pumpswap.priceQuote("solana:token", interval), pumpswap.marketCap("solana:token", interval), pumpswap.fee("solana:token", interval), pumpswap.solUsd("solana:token", interval), pumpswap.liquidity("solana:token", interval). Its account, at the bar: pumpswap.held("solana:token"), pumpswap.heldUsd("solana:token"), pumpswap.worthQuote("solana:token"), pumpswap.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): launchlab (Raydium LaunchLab): Coins launched on letsbonk and the other launch sites built on Raydium's LaunchLab (Solana), bought and sold on each one's own pool until it sells out and the coin migrates to its Raydium AMM pool: a constant-product curve on the pool's virtual reserves plus what it holds. Every trade pays the protocol's, the launch site's and the creator's fees off the SOL; the last buy is filled only up to the tokens the pool has left to sell. The run's USDC is turned into SOL (and back) through Raydium's SOL/USDC pool. Computed with the pool's own math, held to the program's answers on mainnet. Deployments solana; a market is any its deployment lists ("solana:<mint>"). Market reads, a series each: launchlab.price("solana:token", interval), launchlab.priceQuote("solana:token", interval), launchlab.marketCap("solana:token", interval), launchlab.raised("solana:token", interval), launchlab.raisedUsd("solana:token", interval), launchlab.progress("solana:token", interval), launchlab.graduated("solana:token", interval), launchlab.fee("solana:token", interval), launchlab.launchTax("solana:token", interval), launchlab.solUsd("solana:token", interval). Its account, at the bar: launchlab.held("solana:token"), launchlab.heldUsd("solana:token"), launchlab.worthQuote("solana:token"), launchlab.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): virtuals (Virtuals): Agent tokens launched through Virtuals' BondingV5 on Robinhood Chain, bought and sold against VIRTUAL on each one's bonding pair until it graduates to a Uniswap v2 pool: a constant product held to the k the pair was minted with. A buy pays the factory's buy tax off what it spends, a sell its sell tax off what it gets; a launch may add an anti-sniper tax that falls linearly to nothing over its window (60 seconds, 10 or 98 minutes). The buy that leaves the token's reserve at its threshold graduates it. Computed with BondingV5's and its router's own math, to the wei. Deployments robinhood; a market is any its deployment lists ("robinhood:0x…"). Market reads, a series each: virtuals.price("robinhood:token", interval), virtuals.priceQuote("robinhood:token", interval), virtuals.marketCap("robinhood:token", interval), virtuals.raised("robinhood:token", interval), virtuals.raisedUsd("robinhood:token", interval), virtuals.progress("robinhood:token", interval), virtuals.graduated("robinhood:token", interval), virtuals.fee("robinhood:token", interval), virtuals.launchTax("robinhood:token", interval), virtuals.age("robinhood:token", interval). Its account, at the bar: virtuals.held("robinhood:token"), virtuals.heldUsd("robinhood:token"), virtuals.worthQuote("robinhood:token"), virtuals.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): zora (Zora): Coins launched through Zora's factory on Base: creator coins against ZORA, content coins against their creator's coin (or WETH or ZORA), trend coins against ZORA, each trading from its launch on its own Uniswap v4 pool through one of Zora's coin hooks. A swap pays the pool's LP fee as the hook sets it: 99% at a coin's creation, falling linearly to 1% over its first 10 seconds, then 1% (0.01% for a trend coin); a coin on an older hook pays its pool's own. A buy goes from the root currency (ZORA or WETH) through every pool down to the coin in one Universal Router call, and a sale back the same way. There is no curve to graduate from. Computed with v4's own math and the hook's fee, to the wei. Deployments base; a market is any its deployment lists ("base:0x…"). Market reads, a series each: zora.price("base:token", interval), zora.priceQuote("base:token", interval), zora.marketCap("base:token", interval), zora.graduated("base:token", interval), zora.fee("base:token", interval), zora.launchTax("base:token", interval), zora.age("base:token", interval). Its account, at the bar: zora.held("base:token"), zora.heldUsd("base:token"), zora.worthQuote("base:token"), zora.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): clanker (Clanker): Tokens launched through Clanker's v4 factory on Base, each trading from its launch on its own Uniswap v4 pool against the token it was paired with (WETH for nearly all), through one of Clanker's static-fee hooks. A buy pays the token's buy-side LP fee and a sale its sell-side one (often 1% each), and the hook takes 20% of that on top. Before each swap the token's LP locker collects its positions' fees and sells the share its recipients want in the other token through the same pool. A pool's first two minutes run Clanker's sniper auction, in which trades are refused. There is no curve to graduate from. Computed with v4's own math, the hook's fees and the locker's sales, to the wei. Deployments base; a market is any its deployment lists ("base:0x…"). Market reads, a series each: clanker.price("base:token", interval), clanker.priceQuote("base:token", interval), clanker.marketCap("base:token", interval), clanker.graduated("base:token", interval), clanker.fee("base:token", interval), clanker.launchTax("base:token", interval), clanker.age("base:token", interval), clanker.sellFee("base:token", interval). Its account, at the bar: clanker.held("base:token"), clanker.heldUsd("base:token"), clanker.worthQuote("base:token"), clanker.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): flap (Flap): Tokens launched through the Flap Portal on Robinhood Chain, a billion each, bought and sold on Flap's bonding curve until a token's circulating supply reaches its threshold and it moves to a DEX: (1e9 + h − s)·(reserve + r) = k, the supply a buy reaches rounded down and the reserve a sell leaves rounded up. A buy pays the Portal's fee and the token's own buy tax off what it spends, a sell the fee and its sell tax off what it gets; the buy that would carry the supply past the threshold stops there, spends only what reaching it costs and is refunded the rest, and the token moves to its DEX. Computed with Flap's own curve, held to the Portal's quotes and trades to the wei. Deployments robinhood; a market is any its deployment lists ("robinhood:0x…"). Market reads, a series each: flap.price("robinhood:token", interval), flap.priceQuote("robinhood:token", interval), flap.marketCap("robinhood:token", interval), flap.raised("robinhood:token", interval), flap.raisedUsd("robinhood:token", interval), flap.progress("robinhood:token", interval), flap.graduated("robinhood:token", interval), flap.fee("robinhood:token", interval), flap.launchTax("robinhood:token", interval), flap.tax("robinhood:token", interval). Its account, at the bar: flap.held("robinhood:token"), flap.heldUsd("robinhood:token"), flap.worthQuote("robinhood:token"), flap.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Read from its contracts (D1522): metric_omm (Metric): Metric's oracle-priced pools on Robinhood Chain, Ethereum, Base and Arbitrum: every pool its factory made that trades against a dollar, swapped through at what Metric's own quoter answers (a ladder of sizes read each bar for paper, exactly what is sold at the head with real funds, sent through its SimpleRouter), and liquidity put into a pool's bins pro rata through its LiquidityAdder and taken out of the pool, with the share math its bins keep, to the wei. Deployments robinhood, ethereum, base, arbitrum; a market is any its deployment lists ("base:weth-usdc-141"). Market reads, a series each: metric_omm.price("arbitrum:token", interval), metric_omm.bid("arbitrum:token", interval), metric_omm.ask("arbitrum:token", interval), metric_omm.oracleSpread("arbitrum:token", interval), metric_omm.roundTrip("arbitrum:token", interval), metric_omm.liquidityUsd("arbitrum:token", interval). Its account, at the bar: metric_omm.held("arbitrum:token"), metric_omm.heldUsd("arbitrum:token"), metric_omm.lpUsd("arbitrum:token"), metric_omm.lpFeesUsd("arbitrum:token"), metric_omm.lpShare("arbitrum:token"), metric_omm.lpInRange("arbitrum:token"), metric_omm.lpPnlUsd("arbitrum:token"), metric_omm.stateAge("deployment"). A strategy may act on several of these protocols and markets at once. Intents (D1343): earn.apy("usdc", interval) is the best APY among the routes "usdc" names, every lending market and vault above that holds USDC with real funds; "usdc:all" adds the paper-only ones, "usdc:aave,morpho" only those protocols, and "usd" in place of an asset any dollar (USDC, USDT, DAI, USDS, USDe, PYUSD, USDG). earn.deposited() is what earn holds wherever it is, and earn.heldApy() the rate where it is (na with nothing held). Use earn when the asset matters and the protocol does not; use a protocol's own capability to choose it. A strategy uses earn or a protocol's book, not both.
- Husher (D1336): husher.inFlight() is the USD on its way through Husher, and makes it a strategy over more than one market. Husher moves stay on the paper books.

## Orders

- paper.buy({ quote }): spend quote (fees included) at the bar close. paper.buyPct({ pct }): spend pct percent of equity. paper.sell({}): sell the whole position. paper.sellPct({ pct }): sell pct percent of what it holds (spot only). Not enough cash, nothing to sell, or a pyramiding cap reverts the order, with a reason in the backtest.
- Resting orders: paper.buyLimit({ quote, at }) buys at the first later close at or under at; paper.buyStop({ quote, at }) at the first close at or over it (paper.buyPctLimit and paper.buyPctStop take { pct, at }); paper.sellLimit({ at }) sells the position at the first close at or over at, paper.sellStop({ at }) at or under it. at is a number, or an expression the program also publishes with metric() (const entry = …; metric("entry", entry); … at: entry), read at the bar the order is placed. A new order from the same line replaces the one resting, and a resting sell is dropped once the position closes. Spot only.
- perp.long({ usd }), perp.short({ usd }): open or add to a position of that USD notional, closing the other side first. perp.close({}): go flat. perp.long({ usd, maxImpactBps: 10 }) (D1645) takes only what rests within 10 bps of the book's middle as the order goes: in a backtest and on paper it is cut to what the recorded book held that near at the bar (bids10 … asks100, linear between them) and reverts when nothing did; live it is sent as an IOC limit that far from the middle of the venue's live book, so the venue fills what is there and cancels the rest. It reads the run's own book, so it is recorded. Every live perp order, capped or not, is priced from the venue's live book as it is sent, not from the bar it fired on.
- Maker orders on a perp (D1497): perp.bid({ usd, at }) rests a post-only buy of that USD notional at the price at, perp.ask({ usd, at }) a sell; at is any expression (book.bid(market, interval), close * 0.998), read where the order fires. One order rests per line: a later one from the same line replaces it, perp.cancel({}) pulls all of them. A bid over the bar's close (an ask under it) would take, so it reverts. It fills at at, paying the venue's maker fee, on the first later bar that trades through it (a low under a bid, a high over an ask; a touch is not a fill, as others may be ahead of it), netting against the position like any order. Requote each bar with while: while (quoting) { perp.cancel({}); } then while (quoting && perp.sizeUsd() < max) { perp.bid({ usd, at: book.bid(market, interval) }); }. On a project's wallets (a run over its one perp): on paper each fill goes to the wallet's paper perps account at its price and the maker fee; live, after each bar every wallet keeps the run's quotes resting on the venue as post-only orders (an unchanged quote keeps its place, a moved one is cancelled and placed again, all are cancelled when the run stops or holds). The venue's fills are the wallet's: one that fills on a touch the book did not count is logged, and a wallet more than two quotes off the book stops the run. See the starter "Maker quotes".
- Over more than one market: lp.range({ widthPct, usd? }), lp.close({}), perp.hedge({ ratio, minUsd?, maxImpactBps? }), perp.target({ usd, minUsd?, maxImpactBps? }) (maxImpactBps moves toward the target only by what the recorded book held that near its middle, D1645), funds.toPerp({ usd }), funds.toWallet({ usd }). Spot on the strategy's own series too, on paper: paper.buy({ quote }), paper.buyPct({ pct }), paper.sell({}), paper.sellPct({ pct }) fill at the bar close paying the pool's fee, and inPosition()/avgEntry() read that spot book (resting orders are refused there). Spot on a second chain is a strategy of its own on that pool.
- Aave (D1522): aave.supply({ asset, units | usd | pct | all, keep?, slippageBps? }), aave.withdraw({ asset, units | usd | pct | all, keep?, slippageBps? }), aave.borrow({ asset, units | usd | pct, keep?, slippageBps? }), aave.repay({ asset, units | usd | pct | all, keep?, slippageBps? }), aave.eMode({ category }), aave.collateral({ asset, on }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Aave would refuse reverts with the contract's own reason. Spark (D1522): spark.supply({ asset, units | usd | pct | all, keep?, slippageBps? }), spark.withdraw({ asset, units | usd | pct | all, keep?, slippageBps? }), spark.borrow({ asset, units | usd | pct, keep?, slippageBps? }), spark.repay({ asset, units | usd | pct | all, keep?, slippageBps? }), spark.eMode({ category }), spark.collateral({ asset, on }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Spark would refuse reverts with the contract's own reason. Compound (D1522): compound.supply({ asset, units | usd | pct | all }), compound.withdraw({ asset, units | usd | pct | all }), compound.borrow({ asset, units | usd | pct }), compound.repay({ asset, units | usd | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Compound would refuse reverts with the contract's own reason. Morpho (D1522): morpho.deposit({ asset, units | pct | all }), morpho.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Morpho would refuse reverts with the contract's own reason. Euler (D1522): euler.deposit({ asset, units | pct | all }), euler.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Euler would refuse reverts with the contract's own reason. Yearn (D1522): yearn.deposit({ asset, units | pct | all }), yearn.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Yearn would refuse reverts with the contract's own reason. Fluid (D1522): fluid.deposit({ asset, units | pct | all }), fluid.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Fluid would refuse reverts with the contract's own reason. Gearbox (D1522): gearbox.deposit({ asset, units | pct | all }), gearbox.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Gearbox would refuse reverts with the contract's own reason. Spark Savings (D1522): spark_savings.deposit({ asset, units | pct | all }), spark_savings.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Spark Savings would refuse reverts with the contract's own reason. Sky (D1522): sky.deposit({ asset, units | pct | all }), sky.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Sky would refuse reverts with the contract's own reason. Ethena (D1522): ethena.deposit({ asset, units | pct | all }), ethena.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Ethena would refuse reverts with the contract's own reason. Maple (D1522): maple.deposit({ asset, units?, pct?, all? }), maple.withdraw({ asset, units?, pct?, all? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Maple would refuse reverts with the contract's own reason. Uniswap (D1522): uniswap.swap({ market, sell, amountIn | amountOut | all, slippageBps? }), uniswap.route({ market, sell, amountIn | amountOut | usd | all, buy?, via?, hops?, slippageBps? }), uniswap.addLiquidity({ market | nft, amount0 | amount1 | usd | all, widthPct?, tickLower?, tickUpper?, eth?, slippageBps? }), uniswap.removeLiquidity({ market | nft, pct?, eth?, slippageBps? }), uniswap.collectFees({ market | nft, eth? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Uniswap would refuse reverts with the contract's own reason. KyberSwap (D1522): kyberswap.swap({ market, sell, amountIn | usd | all, buy?, slippageBps? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move KyberSwap would refuse reverts with the contract's own reason. CoW Swap (D1522): cow.swap({ market, sell, amountIn | usd | all, buy?, slippageBps? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move CoW Swap would refuse reverts with the contract's own reason. Aerodrome (D1522): aerodrome.swap({ market, sell, amountIn | amountOut | all, slippageBps? }), aerodrome.addLiquidity({ market, amount0 | amount1 | usd | all, tickLower?, tickUpper?, widthPct?, slippageBps? }), aerodrome.removeLiquidity({ market | nft, pct?, slippageBps? }), aerodrome.collectFees({ market | nft }), aerodrome.stake({ market | nft }), aerodrome.unstake({ market | nft }), aerodrome.claimRewards({ market | nft }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Aerodrome would refuse reverts with the contract's own reason. Pendle (D1522): pendle.buy({ asset, usd | pct | all }), pendle.sell({ asset, units?, pct?, all? }), pendle.roll({ asset, to?, pct? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Pendle would refuse reverts with the contract's own reason. marginfi (D1522): marginfi.supply({ asset, units | usd | pct | all }), marginfi.withdraw({ asset, units | usd | pct | all }), marginfi.borrow({ asset, units | usd | pct }), marginfi.repay({ asset, units | usd | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move marginfi would refuse reverts with the contract's own reason. Kamino (D1522): kamino.supply({ asset, units | usd | pct | all }), kamino.withdraw({ asset, units | usd | pct | all }), kamino.borrow({ asset, units | usd | pct }), kamino.repay({ asset, units | usd | pct | all }), kamino.eMode({ category }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Kamino would refuse reverts with the contract's own reason. Kamino Earn (D1522): kamino_earn.deposit({ asset, units | pct | all }), kamino_earn.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Kamino Earn would refuse reverts with the contract's own reason. Jupiter Lend (D1522): jupiter_lend.deposit({ asset, units | pct | all }), jupiter_lend.withdraw({ asset, units | pct | all }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Jupiter Lend would refuse reverts with the contract's own reason. Jito (D1522): jito.deposit({ asset, units | usd | sol | pct | all, slippageBps? }), jito.withdraw({ asset, units | usd | tokens | pct | all, keepSol?, slippageBps? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Jito would refuse reverts with the contract's own reason. Pons (D1522): pons.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), pons.buy({ market, amountIn | usd | pct | all, slippageBps? }), pons.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Pons would refuse reverts with the contract's own reason. pump.fun (D1522): pumpfun.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), pumpfun.buy({ market, amountIn | usd | pct | all, slippageBps? }), pumpfun.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move pump.fun would refuse reverts with the contract's own reason. PumpSwap (D1522): pumpswap.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), pumpswap.buy({ market, amountIn | usd | pct | all, slippageBps? }), pumpswap.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move PumpSwap would refuse reverts with the contract's own reason. Raydium LaunchLab (D1522): launchlab.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), launchlab.buy({ market, amountIn | usd | pct | all, slippageBps? }), launchlab.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Raydium LaunchLab would refuse reverts with the contract's own reason. Virtuals (D1522): virtuals.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), virtuals.buy({ market, amountIn | usd | pct | all, slippageBps? }), virtuals.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Virtuals would refuse reverts with the contract's own reason. Zora (D1522): zora.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), zora.buy({ market, amountIn | usd | pct | all, slippageBps? }), zora.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Zora would refuse reverts with the contract's own reason. Clanker (D1522): clanker.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), clanker.buy({ market, amountIn | usd | pct | all, slippageBps? }), clanker.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Clanker would refuse reverts with the contract's own reason. Flap (D1522): flap.swap({ market, sell, amountIn | usd | pct | all, slippageBps?, keepQuote? }), flap.buy({ market, amountIn | usd | pct | all, slippageBps? }), flap.sell({ market, amountIn | pct | all, slippageBps?, keepQuote? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Flap would refuse reverts with the contract's own reason. Metric (D1522): metric_omm.swap({ market, sell, amountIn | usd | all, slippageBps? }), metric_omm.addLiquidity({ market, usd | amount0 | amount1 | all, bins?, slippageBps? }), metric_omm.removeLiquidity({ market, pct? }). asset names a market; a size is units of the token, usd at the protocol's oracle price, pct of what is there, or all. The program's cash is USDC; a move Metric would refuse reverts with the contract's own reason. Options: <id>.buy({ type: "call" | "put", days, strike?, usd | units }) buys an option expiring at the venue's first 08:00 UTC at least days out, at strike percent of the index (100 without it, at the money), sized by the premium it spends (usd) or by units of the underlying; <id>.sell({ … }) sells one the same way, sized by the collateral it locks in cash (usd: a put locks its strike, a call the index) or by units, taking the premium in; <id>.close({}) closes every option at the model's price. Each settles at expiry at what it is worth against the index. A strategy may hold many at once: a straddle is a call and a put bought on the same bar. A swap venue's token (jupiter, D1501): jupiter.buy({ usd? | pct?, slippageBps?, maxImpactPct?, dexes?, direct? }) buys at market (all the cash without usd or pct; pct is of the cash), refused when its route would cost more than maxImpactPct percent; slippageBps bounds the live fill, dexes ("Orca,Raydium") and direct (one hop) narrow the route. jupiter.sell({ usd? | pct?, … }) sells the same way (pct of the tokens; all without either). jupiter.limitBuy({ usd? | pct?, at, expiresHours?, slippageBps? }) rests a buy under the price, locking its cash, filled at `at` less 0.1% once a later bar trades through it; jupiter.limitSell({ usd? | pct?, at, expiresHours? }) rests a sale over it, locking its tokens; at least $5 each. jupiter.dca({ usd?, orders, everyMinutes, minPrice?, maxPrice? }) spreads usd over orders slices, one each everyMinutes, each bought only inside [minPrice, maxPrice], at least $100 over $50 slices. jupiter.cancel({ orders?: "limit" | "dca" | "all" }) takes back what rests. Every number they take may be an expression (at: px * 0.95). Earn: earn.deposit({ usd? }) puts the wallet's cash on the best route (or where earn already holds), earn.withdraw({ usd? }) takes it back, earn.rebalance({ minGainPct?, costUsd? }) moves all of it to the best route when that pays at least minGainPct more (0.5 points without it), charging costUsd for the move, and does nothing otherwise, so while (earn.deposited() > 0) { earn.rebalance({ minGainPct: 1 }); } is a whole yield router. Live, a move withdraws, bridges home and funds the new route over a few bars.
- Husher: husher.toPerp({ usd, delayMinutes? }) and husher.toWallet({ usd, delayMinutes? }) move funds between the wallet and the perp account that land delayMinutes later (0 to 120, 10 without it), paying transferCost. A move that cannot be done (no funds, a number that is na, a delay out of range, 50 in flight) reverts with why in the log; it never stops the run.
- Backtest and run settings: cash, feeBps, slippageBps, pyramiding (most entries per position), stopPct, takePct, trailPct (exits the broker places at a bar close), leverage on a perp.

## Triggers

- when (cond) { … } fires on the bar cond turns true (including the first bar it is known to be true), once per crossing. A condition that holds all day, such as dayofweek(t) == 2, fires once, on its first bar.
- while (cond) { … } fires at every bar cond holds. It is for actions meant to repeat, and needs a guard that turns false once the action has done its job.
- An if inside a body is read at the bar the trigger fires. Combinators: crossover(a, b), crossunder(a, b), rising(c), falling(c), changed(x), once(c), sustained(c, n), cooldown(hours(6)), hysteresis(x, on, off), every("1d").

## Language

- Numbers, conditions (true, false or na), text, records ({ a: 1 }), and series of each. Arithmetic + - * / %, comparisons < <= > >= == != (they do not chain: 1 < x && x < 3), && || !, cond ? a : b, if (c) a else b. Durations seconds(n), minutes(n), hours(n) are seconds.
- na is "not known yet". It spreads through arithmetic and comparisons; isNa(x) tests it, nz(x, d) replaces it. A computed infinity or a division by zero is na too.
- Math: abs, min, max, clamp(x, lo, hi), ln, exp, sqrt, pow, floor, ceil, round, sign. Series: change(x), x[k], rolling_high(x, n), rolling_low(x, n), median(x, n), percentile(x, n, q), barIndex(x) (the bar number from 0).

## Indicators

- sma(src, len), ema(src, len), wma(src, len), rma(src, len), hma(src, len), dema(src, len), tema(src, len), vwma(src, vol, len), vwap(price, vol, len)
- rsi(src, len) 0 to 100; stoch(close, high, low, len) 0 to 100; cci(src, len); mfi(src, vol, len); obv(src, vol); roc(src, len) percent; momentum(src, len)
- macd(src, fast, slow), macdSignal(src, fast, slow, sig), macdHist(src, fast, slow, sig): one function per line, as macd(close, 12, 26) and macdSignal(close, 12, 26, 9)
- Bollinger: the middle is sma(src, len); bbUpper(src, len, mult), bbLower(src, len, mult); stddev(src, len), zscore(src, len)
- wilderAtr(high, low, close, len) is the ATR as charts draw it; trueRange(high, low, close); atr(src, len) reads one series only
- adx(high, low, close, diLen, adxLen), diPlus(high, low, close, len), diMinus(high, low, close, len); supertrend(high, low, close, factor, len) and supertrendDir(…) (-1 up, 1 down); sar(high, low, close, start, inc, max)
- highest(src, len), lowest(src, len), rolling_sum(src, len), cum(src), linreg(src, len), corr(a, b, len); barssince(cond), valuewhen(cond, src); pivothigh(src, left, right), pivotlow(src, left, right) (known right bars late)
- Market profile (TPO): tpo.<level>(high, low, close, volume, t, window: n, row: r, session: "day"|"week"|"window"|secs, previous: true|false, valueArea: 0.7) reads the last whole session's (previous: true, the default) or the developing session's time-price profile from the last n bars (n ≥ two sessions of bars: 96 on 30m for daily): levels poc, vah, val (time value area), vpoc, vvah, vval (volume), vpocShift (vpoc − poc: where the business was done against where the time was spent), high, low, ibHigh, ibLow (first two bars), poorHigh, poorLow (1 when the extreme row has 2+ blocks: an unfinished auction price tends to revisit), singleAbove, singleBelow (nearest unfilled single print around the close) and nakedPocAbove, nakedPocBelow (nearest untouched session POC). Row 0 picks one from the bars. na until a session is whole.
- Trained models (D1561): ml.linear, ml.ridge (alpha 0.1), ml.lasso (alpha 0.01), ml.elasticNet (alpha, l1Ratio), ml.svr (C, epsilon, kernel) forecast y; ml.logistic (C) answers the chance y is above zero, ml.svm (C, kernel "linear"|"rbf", gamma) its signed margin, ml.knn (k) the mean y of the k nearest rows; ml.boost (trees 100, depth 3, learningRate 0.1, minLeaf 20) is gradient-boosted trees forecasting y, ml.boostClassifier the same trees answering the chance y is above zero: trees catch thresholds and interactions a linear model misses (window ≥ 2·minLeaf). Each is written ml.<kind>(y, [x1, x2, …], window: n, horizon: h, retrain: r, retrainOn: cond, …settings): it learns y h bars after the predictors over the last n rows (no lookahead), refits every r rows (0 = once) or where retrainOn holds, and answers na until the first fit. Settings are numbers or params. Predictors are standardised per fit; up to 64, and an rbf model or knn keeps window ≤ 1000. See the example "Perp lasso forecast, long and short" and the starter "ML forecast".

## More

- fn name(a: Series<number>, n: count): Series<number> { return …; } defines a function. enum and match name regimes. The full reference is flowscript/docs/reference.md in the repository.
- Libraries: save_library({ name, source, description? }) keeps fn definitions under a name (lowercase, up to 40), and `import "name";` after the uses line gives a strategy its functions. A strategy keeps the library as it was when the strategy was saved; list_libraries says which strategies import each one and whether they have its current version, and saving the strategy again takes the new one.

## Workflow

- list_strategies for the starters, list_backtest_sources for what a series can be.
- save_strategy({ name, source, params }). A refusal names the line, or the parameter, and what to change.
- backtest_strategy({ id, series, interval }). Check that orders filled (a revert says why), that the trade count is what the idea implies, and compare return with buy-and-hold over more than one window.
- A save that compiles can still return warnings (FS09xx, by line): a condition that is never true (FS0901), a while that buys at every bar (FS0902), a const nothing reads (FS0903), a divide by 0 (FS0904), bps(n) used as a number, which is n and not the rate n / 10000 (FS0905). Each is a line that does something other than it seems to say; fix it unless you meant it.
- Publish what the strategy decides on with metric(name, value): a backtest returns every metric per bar, which is how to see why a trigger did or did not fire. A backtest also lists under quiet each when or while that never fired, and whether its condition was na throughout, never true, or true and held back.
- test_strategy({ id, tests }): feed it bars you write (a step up, a crash, a gap, a flat market) and say what it should do. Save the cases that pass with save_strategy({ id, tests }); every later save runs them (testReport), and run_strategy refuses while one fails. See testing.
- run_strategy({ id, series, interval }) keeps a paper book; with account it trades a project's wallets, and live funds only from a connection allowed to trade live. A running strategy stops itself if its replay stops matching the orders it already sent.

## Testing

A test feeds a strategy bars you write and checks what it does with them, on the backtest's paper broker (1000 cash, 30 bps). It is how to know a strategy buys the move it is meant to, holds still in a flat market, survives a gap, and keeps doing so after an edit. A check is compiled into the strategy and run by the same evaluator a backtest uses.

### Cases

- A case: { name, bars?, ohlc? or sources?, params?, paper?: false, expect }. bars is how many (the longest list, or 100), counted from 0. params sets arguments (the defaults otherwise).
- ohlc: a close series; bars.open is the close before, high and low their max and min, volume 1000. sources: { 'bars.close': …, 'bars.volume': … } gives each read itself.
- A series is a list ([100, 101, null, 103]; null is na; a short list holds its last value) or a generator: { constant: 100 }, { ramp: [100, 120] }, { sine: { mid: 100, amp: 5, period: 20 } }, { steps: [[0, 100], [30, 130]] }, { walk: { from: 100, step: 1, seed: 7 } }, each with na: [bar, …] to blank bars. `runs: 30` runs a case 30 times, each walk with the next seed: the way to say "over any market, it never sells more than it holds".

### Expectations

- { check: "<condition over its consts>", at: N } holds at bar N; at: "ever" (the default), "always" (every bar it is known, and at least one), "never" or "last"; from and to bound the bars.
- { fires: "paper.buy", at?, count?, args? }: the order was placed at that bar, that many times (count: 0 is never), with those arguments ({ quote: 100 }).
- { fills: "paper.buy", at?, count? } and { reverts: … }: the broker filled, or reverted, the order.
- { paused: "paper.buy", at?, count?, by?: "drawdown" }: the strategy's invariants cage refused that entry, as the live host would (a sell is never refused).
- { metric: "rsi", at: N or "last", equals, within? } or { metric, at, na: true }: a published value.
- { warns: "FS0901" } or { warns: "none" }: what the compiler warns of.

### Example: tests for "EMA crossover"

```json
[
  {
    "name": "buys the step up once it crosses, and never sells",
    "bars": 80,
    "ohlc": {
      "steps": [
        [
          0,
          100
        ],
        [
          30,
          130
        ]
      ]
    },
    "expect": [
      {
        "fires": "paper.buy",
        "at": 30,
        "args": {
          "quote": 100
        }
      },
      {
        "fills": "paper.buy",
        "at": 30
      },
      {
        "fires": "paper.sell",
        "count": 0
      },
      {
        "check": "f > s",
        "at": "last"
      }
    ]
  },
  {
    "name": "sells the fall after it",
    "bars": 120,
    "ohlc": {
      "steps": [
        [
          0,
          100
        ],
        [
          30,
          130
        ],
        [
          70,
          90
        ]
      ]
    },
    "expect": [
      {
        "fires": "paper.sell",
        "at": 70
      },
      {
        "fills": "paper.sell",
        "at": 70
      }
    ]
  },
  {
    "name": "holds still in a flat market",
    "sources": {
      "bars.close": {
        "constant": 100
      }
    },
    "expect": [
      {
        "fires": "paper.buy",
        "count": 0
      }
    ]
  }
]
```

## Examples

### Trend with a stop

An entry on a crossover, an exit on the cross back or a stop under the average entry, read from the book with inPosition() and avgEntry().

Params:

```json
[{"name":"fast","label":"Fast EMA","kind":"number","default":12,"min":2,"max":200},{"name":"slow","label":"Slow EMA","kind":"number","default":26,"min":3,"max":400},{"name":"stopPct","label":"Stop under entry (%)","kind":"number","default":4,"min":0.1,"max":50},{"name":"size","label":"Buy size (quote)","kind":"number","default":100,"min":1}]
```

```flowscript
const close = bars.close(market, interval);
const f = ema(close, fast);
const s = ema(close, slow);
const stop = avgEntry() * (1 - stopPct / 100);
metric("fast", f);
metric("slow", s);
metric("stop", inPosition() ? stop : na);

when (crossover(f, s) && !inPosition()) { paper.buy({ quote: size }); }
when (inPosition() && (crossunder(f, s) || close < stop)) { paper.sell({}); }
```

### RSI reversion, one entry

A level read as an edge: when fires once as RSI drops under the line, and !inPosition() keeps a second dip from adding to the position.

Params:

```json
[{"name":"length","label":"RSI length","kind":"number","default":14,"min":2,"max":100},{"name":"buyBelow","label":"Buy below","kind":"number","default":30,"min":1,"max":50},{"name":"sellAbove","label":"Sell above","kind":"number","default":65,"min":50,"max":99},{"name":"size","label":"Buy size (quote)","kind":"number","default":100,"min":1}]
```

```flowscript
const close = bars.close(market, interval);
const r = rsi(close, length);
metric("rsi", r);
when (r < buyBelow && !inPosition()) { paper.buy({ quote: size }); }
when (inPosition() && r > sellAbove) { paper.sell({}); }
```

### Breakout on volume, ATR exit

A breakout of the previous bars' high ([1], so the bar being tested is not in its own window), confirmed by volume, and an exit an ATR multiple under the recent high.

Params:

```json
[{"name":"lookback","label":"Breakout bars","kind":"number","default":20,"min":2,"max":500},{"name":"volMult","label":"Volume over average (x)","kind":"number","default":1.5,"min":0,"max":20},{"name":"atrLen","label":"ATR length","kind":"number","default":14,"min":2,"max":200},{"name":"atrMult","label":"Exit ATRs under the high","kind":"number","default":3,"min":0.5,"max":20},{"name":"size","label":"Buy size (quote)","kind":"number","default":100,"min":1}]
```

```flowscript
const close = bars.close(market, interval);
const hi = bars.high(market, interval);
const lo = bars.low(market, interval);
const vol = bars.volume(market, interval);
const top = highest(hi, lookback)[1];
const range = wilderAtr(hi, lo, close, atrLen);
const trail = highest(close, lookback) - atrMult * range;
metric("top", top);
metric("trail", trail);

when (close > top && vol > volMult * sma(vol, lookback) && !inPosition()) { paper.buy({ quote: size }); }
when (inPosition() && close < trail) { paper.sell({}); }
```

### Trend filter, percent of equity

Sizing by what the book is worth with paper.buyPct, only above the long average, up to two entries (pyramiding: 2, so the entry is not gated on !inPosition()), and the exits the backtest places itself (stopPct, takePct) instead of in the source.

Params:

```json
[{"name":"filter","label":"Trend average","kind":"number","default":100,"min":10,"max":400},{"name":"entry","label":"Entry average","kind":"number","default":10,"min":2,"max":100},{"name":"pct","label":"Buy (% of equity)","kind":"number","default":25,"min":1,"max":100}]
```

```flowscript
const close = bars.close(market, interval);
const trend = sma(close, filter);
const fast = ema(close, entry);
metric("trend", trend);
metric("fast", fast);
when (close > trend && crossover(close, fast)) { paper.buyPct({ pct: pct }); }
when (inPosition() && crossunder(close, trend)) { paper.sell({}); }
```

Backtest with: `{"pyramiding":2,"stopPct":6,"takePct":15}`

### Pullback limit in a trend

A resting order: once the close is over its trend, a buy rests a few percent under the trend (paper.buyLimit at a level the program also publishes with metric()), and fills at the first close at or under it; a resting stop under the entry sells it.

Params:

```json
[{"name":"trendLen","label":"Trend average","kind":"number","default":50,"min":5,"max":400},{"name":"dipPct","label":"Buy under the trend (%)","kind":"number","default":2,"min":0.1,"max":30},{"name":"stopPct","label":"Stop under the trend (%)","kind":"number","default":8,"min":0.5,"max":50},{"name":"size","label":"Buy size (quote)","kind":"number","default":100,"min":1}]
```

```flowscript
const close = bars.close(market, interval);
const trend = ema(close, trendLen);
const entry = trend * (1 - dipPct / 100);
const stop = trend * (1 - stopPct / 100);
metric("trend", trend);
metric("entry", entry);
metric("stop", stop);
when (close > trend && !inPosition()) { paper.buyLimit({ quote: size, at: entry }); }
when (inPosition()) { paper.sellStop({ at: stop }); }
when (inPosition() && close > trend * 1.05) { paper.sell({}); }
```

### Smaller buys when volatile

Sizing by a computed value is refused, so volatility picks between two fixed sizes, each its own when. ATR as a percent of price is the volatility.

Params:

```json
[{"name":"calmBelow","label":"Full size below ATR (%)","kind":"number","default":2.5,"min":0.1,"max":50},{"name":"maxAtrPct","label":"No entry above ATR (%)","kind":"number","default":4,"min":0.1,"max":50},{"name":"calmSize","label":"Calm buy (quote)","kind":"number","default":200,"min":1},{"name":"busySize","label":"Volatile buy (quote)","kind":"number","default":100,"min":1}]
```

```flowscript
const close = bars.close(market, interval);
const hi = bars.high(market, interval);
const lo = bars.low(market, interval);
const fast = ema(close, 20);
const slow = ema(close, 50);
const atrPct = wilderAtr(hi, lo, close, 14) / close * 100;
const golden = crossover(fast, slow);
metric("atr_pct", atrPct);

when (golden && atrPct < calmBelow && !inPosition()) { paper.buy({ quote: calmSize }); }
when (golden && atrPct >= calmBelow && atrPct < maxAtrPct && !inPosition()) { paper.buy({ quote: busySize }); }
when (inPosition() && crossunder(fast, slow)) { paper.sell({}); }
```

### Daily buy in the session

Time: every("1d") fires once a day, and bars.time with hour() and dayofweek() keeps it to weekday hours (UTC).

Params:

```json
[{"name":"size","label":"Buy each day (quote)","kind":"number","default":25,"min":1},{"name":"fromHour","label":"From hour (UTC)","kind":"number","default":14,"min":0,"max":23}]
```

```flowscript
const close = bars.close(market, interval);
const t = bars.time(market, interval);
const weekday = dayofweek(t) >= 2 && dayofweek(t) <= 6;
metric("close", close);
when (weekday && hour(t) >= fromHour && cooldown(hours(20))) { paper.buy({ quote: size }); }
```

### Perp trend with a funding filter (perps market)

On a perps market: long and short on one average, skipping the side funding makes expensive, and flat when funding runs hot either way. A long closes a short first; perp.close({}) goes flat.

Params:

```json
[{"name":"len","label":"Average","kind":"number","default":50,"min":2,"max":400},{"name":"maxFunding","label":"Most funding to pay (%/h)","kind":"number","default":0.01,"min":0},{"name":"size","label":"Position (USD notional)","kind":"number","default":500,"min":1}]
```

```flowscript
uses { bars: "^1", funding: "^1", perp: "^1" }
const close = bars.close(market, interval);
const avg = ema(close, len);
const rate = nz(funding.rate(market, interval), 0) * 100;
metric("avg", avg);
metric("funding_pct_h", rate);

when (crossover(close, avg) && rate < maxFunding) { perp.long({ usd: size }); }
when (crossunder(close, avg) && rate > 0 - maxFunding) { perp.short({ usd: size }); }
when (abs(rate) > 2 * maxFunding) { perp.close({}); }
```

### Perp lasso forecast, long and short (perps market)

A trained model (ml.*, D1561) on a perps market: a lasso regression learns the return over the next horizon bars from four features over the last trainBars bars, refits every retrain bars, and the run goes long on a forecast over the band, short under minus it, and flat when it fades. The same model is fitted in a backtest, the optimizer and a live run; its settings are params, so the optimizer sweeps them. Swap ml.lasso for ml.ridge, ml.svr or ml.elasticNet the same way; ml.logistic answers the chance y is above zero and ml.svm its margin.

Params:

```json
[{"name":"trainBars","label":"Training window (bars)","kind":"number","default":300,"min":50,"max":5000},{"name":"horizon","label":"Forecast horizon (bars)","kind":"number","default":4,"min":1,"max":100},{"name":"retrain","label":"Refit every (bars, 0 = once)","kind":"number","default":24,"min":0,"max":5000},{"name":"alpha","label":"Lasso penalty","kind":"number","default":0.01,"min":0,"max":10},{"name":"bandPct","label":"Forecast to trade (%)","kind":"number","default":0.1,"min":0,"max":100},{"name":"size","label":"Position (USD notional)","kind":"number","default":500,"min":1}]
```

```flowscript
uses { bars: "^1", funding: "^1", perp: "^1" }
const close = bars.close(market, interval);
// The target: the return over the last `horizon` bars, known at each bar.
const ret = close / close[horizon] - 1;
const features = [
  close / close[1] - 1,
  close / close[6] - 1,
  close / ema(close, 24) - 1,
  rsi(close, 14) / 100 - 0.5,
];
// Learns the return `horizon` bars ahead; na until the first fit, so nothing trades before it.
const forecast = ml.lasso(ret, features, window: trainBars, horizon: horizon, retrain: retrain, alpha: alpha) * 100;
metric("forecast_pct", forecast);

when (forecast > bandPct) { perp.long({ usd: size }); }
when (forecast < 0 - bandPct) { perp.short({ usd: size }); }
when (abs(forecast) < bandPct / 4) { perp.close({}); }
```

## Pitfalls

### while buys at every bar (FS0902)

Wrong:

```flowscript
while (rsi(close, 14) < 30) { paper.buy({ quote: 100 }); }
```

Right:

```flowscript
when (rsi(close, 14) < 30 && !inPosition()) { paper.buy({ quote: 100 }); }
```

while fires at every bar its condition holds, so a long dip spends all the cash a bar at a time. when fires once, the bar the condition turns true. Use while only for something meant to repeat (rebalancing a range, topping up margin), and guard it.

### when adds to a position on every new crossing

Wrong:

```flowscript
when (close < lower) { paper.buy({ quote: 100 }); }
```

Right:

```flowscript
when (close < lower && !inPosition()) { paper.buy({ quote: 100 }); }
```

Each time the condition turns true again it buys again. pyramiding is unlimited unless the backtest or the run sets it, in a backtest and live alike. For one entry, gate on !inPosition(). For up to n entries, drop that gate and pass pyramiding: n (a buy past it reverts). A second entry needs the condition to turn false and true again while the position is open.

### An exit that fires while flat

Wrong:

```flowscript
when (r > 70) { paper.sell({}); }
```

Right:

```flowscript
when (inPosition() && r > 70) { paper.sell({}); }
```

An exit condition can turn true while nothing is held, and each time the sell reverts ("there is no `bars.close` position to sell": bars.close is the price the position is held at). Nothing is lost, but the backtest fills with reverts that hide real ones. Gate exits on inPosition() on a pool or a feed.

### timeInPosition() is seconds, not bars

Wrong:

```flowscript
when (inPosition() && timeInPosition() > 200) { paper.sell({}); }   // 200 seconds: sells the next bar
```

Right:

```flowscript
when (inPosition() && timeInPosition() > hours(200)) { paper.sell({}); }
```

timeInPosition() counts seconds since the position opened, and seconds(n), minutes(n) and hours(n) are seconds too. For a count of bars, divide by the bar's length: const t = bars.time(market, interval); timeInPosition() / (t - t[1]).

### An order size is a number or an argument, never computed

Wrong:

```flowscript
paper.buy({ quote: wallet.cash() * 0.1 });
```

Right:

```flowscript
paper.buyPct({ pct: 10 });
```

Over one market, quote, pct and usd must be a number or a strategy argument as written; a computed size is refused when the strategy is saved. To size by the book, use paper.buyPct({ pct }). Only a strategy over more than one market (lp, perp.hedge, funds) takes computed numbers, and each one must also be published with metric().

### A window that includes the bar it is compared with (FS0901)

Wrong:

```flowscript
when (close > highest(close, 20)) { … }
```

Right:

```flowscript
when (close > highest(close, 20)[1]) { … }
```

highest(close, 20) includes this bar's close, so close is never above it. [1] reads the window as of the bar before.

### na is not false, and it spreads

Wrong:

```flowscript
when (!(close > avg)) { paper.sell({}); }
```

Right:

```flowscript
when (close < avg) { paper.sell({}); }
```

Until an indicator has enough bars it is na, and so is anything computed from it. A comparison with na is na, ! of na is na, and a when never fires on na. Write the condition you mean rather than negating its opposite, and use nz(x, 0) or isNa(x) where a missing value should count as something.

### One missing bar poisons a fold forever

Wrong:

```flowscript
const n = fold(close, 0, (st, x) => x > 100 ? st + 1 : st);
```

Right:

```flowscript
const n = fold(close, 0, (st, x) => isNa(x) ? st : (x > 100 ? st + 1 : st));
```

fold carries its state from bar to bar. If one sample is na and the step does not test for it, the state turns na and stays na for the rest of the run. Guard every fold step with isNa(x) ? st : …. The built-in indicators already do.

### Early bars of sma, stddev and highest are partial

Wrong:

```flowscript
when (crossover(close, sma(close, 200))) { … }   // can fire on bar 2 of a backtest
```

Right:

```flowscript
when (barIndex(close) >= 200 && crossover(close, sma(close, 200))) { … }
```

sma, stddev and the rolling windows average what they have until the window fills, while ema, rsi and atr are na until warmed up. A running strategy replays 500 bars before it decides anything, but a backtest decides from its first bar. If the first trades of a backtest look odd, gate on barIndex.

### A condition is not a number

Wrong:

```flowscript
metric("up", close > open);   const score = (a > b) + (c > d);
```

Right:

```flowscript
const score = (a > b ? 1 : 0) + (c > d ? 1 : 0);
```

Conditions and numbers are different types, and arithmetic on a condition is a compile error. A metric may publish a condition (true is 1), but to compute with one, turn it into a number with ? 1 : 0.

### A parameter cannot reuse a given name

Wrong:

```flowscript
params: [{ name: "low", … }, { name: "close", … }]
```

Right:

```flowscript
params: [{ name: "buyBelow", … }, { name: "exitAbove", … }]
```

market, interval, the bar fields (open, high, low, close, volume, time), the modules (bars, paper, perp, funding, pool, feed, lp, funds, wallet), leverage, na, true and false are taken. A parameter named after one is refused when the strategy is saved. Names are lowerCamel, up to 24 characters.

### On a pool, a live sell sells everything

Wrong:

```flowscript
Expecting paper.sell to sell part of the position, or quote to be what a live wallet spends.
```

Right:

```flowscript
Size a live run with account.ethPerBuy, and treat paper.sell({}) as "go flat".
```

paper.sell({}) always sells the whole position. A run trading a project's wallets on a pool spends ethPerBuy from each wallet per buy, not the order's quote, and a sell sells each wallet's whole holding (account.pick: n sends each buy from n wallets drawn at random, so a desk's buys spread over its wallets). On a perp, each wallet sends the order's own USD notional, or with account.sizing match is brought to the book's position as a share of its own equity.
