> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xarchive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Algos

> Scripts that trade your account through the fixed order types: a chase, a fast stop, a take profit, armed in the tab or on 0xArchive's server.

An algo is a script that places real orders. It declares `algo()` in place of `indicator()`, reads its settings and whatever data it needs, and says in `algo.onArm` which orders to place. When you press **Arm**, the script runs that function once, and the orders it makes are placed: in this tab on Paper or Live, or on 0xArchive's server.

```ts theme={"theme":"github-dark"}
algo('Chase', { role: 'entry', overlay: true });

const side = input.side('Side', 'buy');
const size = input.size('Size', { usd: 1000 });
const worst = input.price('Worst price', null, { drag: true });

algo.onArm(() => {
  order.chase({ side, size, limitPx: worst });
});
```

Every built-in algo in the Algo picker (Chase, Fast stop, Time stop, Take profit, OCO) is a script like this one, and so are its two [strategies](#strategies), Book lean and Flush absorbed. **Edit a copy** opens it in the editor, and your edited copy is an algo of your own, which runs the same way.

## Runs as orders

An algo **runs as orders** when every `order.*` call sits inside the function you give `algo.onArm`. Arming then places fixed orders: a chase with its prices, a stop at its price, a condition with its test. These are the same order types the server runs, so an algo that runs as orders can run on the server with your tab closed, edited or not.

An `order.*` call anywhere else, in `chart.onBar` or a book's `onUpdate`, makes the algo decide later, on live events. The picker tags it **Script**. A Script algo arms in this tab, on Paper or Live. The server trades a strategy's triggers and plans, nothing else: a Script algo that is not a strategy is not offered Server, and a strategy's own order calls are not sent there. [Live logic](#live-logic) below says how it runs.

The picker and the settings show which it is before you arm anything. The compiler decides from the source: keep order calls lexically inside `onArm`'s function. A helper function declared at the top level and called from `onArm` counts as outside, so declare the helper inside `onArm` if it places orders.

## The order types

| Call | What it places |
| - | - |
| `order.chase({ side, size, limitPx, ... })` | A post-only order at the best price that follows it until it fills, never past `limitPx` |
| `order.fastStop({ px, source, size, ... })` | Closes the position you hold when the mark, the best bid or ask, or the last trade reaches `px` |
| `order.timeStop({ after, size, ... })` or `({ at })` | Closes the position you hold after a hold, or at a time |
| `order.oco(a, b)` | Two resting limits: the first to fill cancels the other |
| `order.when(trigger, () => order.X(...))` | When the trigger's test holds, the one order the callback makes: `order.reduce`, `order.close`, `order.limit` or `order.market` |

Each call is checked against the order type's own ranges when the algo plans. A value out of range is refused with the same words the server uses, before anything is sent.

`trigger.mark`, `trigger.book` (the mid), `trigger.trade`, `trigger.funding` and `trigger.oiUsd` are the prices the server watches itself. Their tests are `'above'`, `'below'`, `'crossesAbove'` and `'crossesBelow'`. A trigger outside this list, such as a book lean, needs code to watch it, which is live logic.

## Sizes

A size is one of `{ usd }`, `{ coins }`, `{ pctOfPosition }`, `{ pctOfEquity }`, or `{ riskUsd, stop }`. The last is the size that loses `riskUsd` if the price reaches `stop`, counted from the order's price. An exit sized in dollars or as a share of equity is turned into coins at the mark, rounded down to the market's size step.

## Reading the account

`account()` gives the account as it stands when the algo plans: `position` (its side, size in coins, entry price and notional), `equity` and `mark`. Read it inside `algo.onArm`; elsewhere it reads flat. A stop protects the position the account holds at that moment, in whichever direction it faces:

```ts theme={"theme":"github-dark"}
algo('ATR stop, half at 2R', { role: 'protect', overlay: true });

const mult = input.float('ATR multiple', 1.5, { step: 0.25 });
const chart = bars();
const atr = ta.atr(14);
const acct = account();
chart.onBar(() => { atr.update(chart); });

algo.onArm(() => {
  const p = acct.position;
  const long = p.side === 'long';
  const stop = long ? p.entryPrice - mult * atr.value : p.entryPrice + mult * atr.value;
  const target = p.entryPrice + 2 * (p.entryPrice - stop);
  order.fastStop({ px: stop });
  order.when(trigger.mark(long ? 'crossesAbove' : 'crossesBelow', target), () => order.reduce({ pctOfPosition: 50 }));
});
```

The script runs over the market's history first, as an indicator does, so `atr.value` is warm by the time you arm it.

## Roles

`role: 'protect'` promises the algo only reduces your position: every order it makes must be an exit or reduce-only, or arming is refused. `'entry'` may add to the position, and `'both'` does either. On Live, an entry obeys the account's kill switch; a protective algo keeps working under it.

## Settings for trading

`input.price`, `input.size`, `input.side`, `input.percent` and `input.duration` draw the fields a trader expects: a price, an amount with its unit, buy or sell, a percent, and a time written as `'30s'`, `'5m'` or `'4h'` (read in seconds). Group the ones most people leave alone with `group: 'More'`, and the settings fold them.

`algo.describe(() => '...')` gives the sentence the settings lead with. Without it, the settings word the orders themselves.

## What the host adds

Every algo's settings carry rows the script can't change: the **market**, where it **runs** (this tab or the server), the **account** (Paper or Live; the server is always Live), and when it **ends**. Below them, the settings list in words the orders arming will place, from the account as it is now. **Arm** plans once more at the press, so the orders sent are the account's at that moment. On the server, **Preview** asks the server to check them first.

Several orders from one `onArm` are placed one after another. If the server refuses one, those before it stay armed, and the settings say how many.

## Live logic

A Script algo keeps running after you arm it. Each order call it makes on a live event, a closed bar or a book update or a trade after the arm, is a request, and the tab checks it before anything is placed:

* **Stop first.** An entry needs a working stop from the same algo on that market: an `order.fastStop`, an `order.timeStop`, or an `order.when` that closes or reduces. Place it in `algo.onArm`.
* **Entries.** The settings' **Entries** row says how many times it may open or add to a position on one arm. One by default.
* **Entry caps.** At most 10 entries a minute and 200 a day on the account, across every algo.
* **The kill switch** stops entries on Live while server trading is signed in.
* **`trades.maxPositionUsd`**, when the script declares it, caps the position an entry may take it to.

Exits and reduce-only orders skip these: a stop is never held back. A plain `order.market`, `order.limit`, `order.reduce` or `order.close` places at once.

In history, or before you arm it, an order call sends nothing. Its ref says which: `'sent'` on a live event, `'none'` otherwise. Count what you sent from the ref, so the history run doesn't use it up:

```ts theme={"theme":"github-dark"}
algo('Lean entry', { role: 'both' });
const ob = book({ levels: 10 });
const acct = account();
const sent = state.live(false);

algo.onArm(() => {
  order.fastStop({ px: acct.mark * 0.98, tag: 'stop' });
});

ob.onUpdate((b) => {
  if (sent.value) return;
  const bid = b.bids.reduce((s, l) => s + l.size, 0);
  const ask = b.asks.reduce((s, l) => s + l.size, 0);
  if (bid / (bid + ask) < 0.65) return;
  const ref = order.chase({ side: 'buy', size: { usd: 1000 }, tag: 'entry' });
  if (ref.state === 'sent') sent.value = true;
});

acct.onOrder((o) => {
  if (o.state === 'refused') log.warn(o.tag + ' refused: ' + o.words);
});
```

`account()` reads the account as the tab last saw it while the algo is armed. `acct.onOrder` hears each answer and change to the algo's orders, refusals included, and `acct.onFill` each fill; both run on live events only. `order.cancel('entry')` cancels the working orders made with that tag.

**Disarm** stops the script and cancels its working entries; its stops keep protecting the position. It also ends at its end time, when its script fails, or once its entries are used and its orders have ended. A reload ends it.

## Quoting

`order.quote({ bidPx, askPx, size })` quotes both sides: a post-only bid and ask rest in the account, each of `size` (`{ usd }` or `{ coins }`). Call it from a live handler as often as you like. Each call moves the quotes to its prices, and `null` pulls that side. A quote whose price moved less than half a basis point stays where it is, keeping its place in the queue; a moved one goes to the back of its new level. The tab applies at most four calls a second, the newest.

```ts theme={"theme":"github-dark"}
algo('Quote the spread', { role: 'both', trades: { maxPositionUsd: 3000 } });
const ob = book({ levels: 10 });
ob.onUpdate((b) => {
  const mid = (b.bids[0].price + b.asks[0].price) / 2;
  order.quote({ bidPx: mid * (1 - 8 / 10_000), askPx: mid * (1 + 8 / 10_000), size: { usd: 1000 } });
});
```

A quoting algo needs `trades: { maxPositionUsd }`: the position, valued at the mark, never passes it. The side that would take it past is pulled, and the other side keeps quoting, which works the position back. Quotes are not entries: the entry caps and the stop-first rule above do not apply to them, the cap does. **Disarm** pulls its quotes and leaves the position; **Flatten** pulls them and closes the position at market. Quoting runs on Paper. The built-in **Quote the spread** leans its quotes against what it holds.

A fill pulls at once whatever the cap no longer allows; nothing is placed again until the script's next call says where. Quoting combines with exits: the built-in **Fade the extremes** rests its bid and ask at the edges of the 1-minute Bollinger band, and once a side fills it places `order.fastStop` and `order.timeStop` for that position, quotes only the take profit (`{ coins }` of the whole position, at the band's middle), and cancels its exits with `order.cancel` when the position is flat again.

## Strategies

A **strategy** is an algo that stays on: `algo(name, { lifetime: 'standing' })`. Its script finds the moments to trade (`algo.trigger`), and you steer it while it runs. Each trigger trades only when all of these hold:

* you have armed it;
* the bias allows that side;
* its price is inside one of the strategy's zones, if it has any;
* the governor has room for the day;
* no cooldown is running.

Any other trigger goes in its journal as **would have traded**, with the reason.

```ts theme={"theme":"github-dark"}
algo('Book lean', { lifetime: 'standing', trades: { maxPositionUsd: 20000 } });

const risk = control.number('Risk per trade', 50, { min: 1, max: 500, step: 1, unit: 'usd', history: 50 });
const stopBps = control.number('Stop', 40, { min: 10, max: 200, step: 5, unit: 'bps', history: 40 });

algo.bias({ owner: 'trader', initial: 'both' });
algo.governor({ dailyLossUsd: 150, maxTrades: 4 });

const ob = book({ levels: 10 });
ob.onUpdate((b) => {
  // ...when the book tips to the bids:
  algo.zones([{ lo: b.bids[b.bids.length - 1].price, hi: b.asks[0].price, side: 'long', label: 'Bid stack' }]);
  algo.trigger({ side: 'long', price: b.asks[0].price, why: 'Bids 79% of the top 10' });
});

algo.plan(() => ({
  entry: { how: 'cross', worstBps: 20 },
  size: { riskUsd: risk.value },
  stop: { bps: stopBps.value },
  targets: [{ r: 2, pct: 100 }],
  timeStop: '60m',
}));
```

**Controls** are how you steer it now. `control.number`, `choice`, `toggle`, `price` and `button`, at most 16, declared at the top level like inputs. A change is an event the script reads from its next handler: it never re-runs the script, and the journal records it. A `control.price` is a line on the chart you drag. In history a control reads its `history` value, and a button never fires.

**The plan** turns a trigger into a trade, in your terms. It sets the entry (`cross`, or `chase` with a worst price) and the size (`riskUsd`, `notionalUsd` or `equityPct`). It must set a stop: `bps`, a `price`, or `beyondZone`. Targets in R and a time stop are optional. When a trigger trades, its **stop goes in first**, protecting the side the entry will open, and then the entry. Once the entry fills, the stop **rests on the exchange** as a reduce-only stop market for what filled, and each target as a reduce-only take-profit market, so they protect the position when the tab closes. The tab watches the stop only until the exchange lists it, or for the whole trade if the exchange refuses it. The time stop runs in the tab. Every order goes through the admission and caps in [Live logic](#live-logic). A strategy places entries only through its plan: an entry its handlers place directly is refused.

**Start on Paper** or **Start on Live** in its settings starts it **Watching**, on that account: bias Off and no shots, so nothing trades until you arm it. Live places real orders with your trading key, each through the same checks. A strategy never trades a market where you hold a position it didn't open. Its row in Running is where you drive it, and its state is one word at the row's start: **Watching**, **Armed** with its shots and bias ("Armed 2 · Long"), **In trade**, **Paused** or **Stopped**.

* **Bias:** Long, Short, Both or Off. Off always wins, even when the script sets the bias with `algo.bias.set`.
* **Shots** and **Arm**: pick how many trades it may open (1, 2, 3, 5, or **Auto** for no limit) and press **Arm**. Each trade it opens takes one; at none it watches again. While it's armed, Shots says what is left and the button is **Disarm**. On Live, Arm asks a second time, naming the account.
* **Armed for** and **Each trade**, in its details: how many minutes an arm lasts, and **Confirm** or **At once**. Confirm comes first on Live. With Confirm, each accepted trigger waits on a card showing the trade it will place. **Trade** places it. **Skip**, or 30 seconds passing, places nothing and keeps the shot.
* **The menu** (⋯) holds what ends something, each asking again on the row. **Flatten & disarm** turns entries off, cancels its entry and its resting stop and targets, and closes the position its trade opened. **Stop** ends the strategy: its resting stop and targets stay on the exchange and keep protecting the position. Its row in History says what still rests.
* **The state and the next trade**, and the governor's day: "Day P\&L −$40 of −$150 · 1 of 4 trades". The day counts every trade on the account: on Live, today's closed P\&L less fees from the account's fills, plus what is open. While it can't be read, it takes no entries. At the loss limit it **halts** until the next UTC day. **Reset** lifts the halt and turns the day's loss limit off until tomorrow.
* **Its controls,** live.
* `panel.checklist` and `panel.log`, if the script writes them: what it waits for, and its own notes.
* **The journal:** the last 30 events.

Its zones are bands on its market's charts while its settings are open or it runs.

### On history

A strategy replays its history with fills, and its settings show the result under **On history**: trades, win rate, net, drawdown and costs, from the strategy() account's fees, book slippage and funding.

The replay assumes what you would own live: armed for every trigger and its declared bias, unless `algo({ backtest: { capital, assume: { bias, armed } } })` says otherwise. The governor counts the simulated day. When it halts the strategy, the replay arms it again at the next UTC day, when the halt lifts, as you would. With `armed: 'script'`, only the script's own `algo.arm` arms it.

It trades at the bar tier:

* the entry fills at the next bar's open;
* the stop and the first target rest as one exit;
* the time stop counts from the fill: its exit goes in at the first bar close past it and fills at the next bar's open, so a one-bar time stop goes out at the open after the bar it filled in.

In this tab only a trigger on a closed chart bar is replayed: a strategy that reads the live book or the trade stream has no history here, so On history has nothing to show for Book lean. Flush absorbed decides on closed bars from 0xArchive's open interest, so its history and its live run are the same signal.

A backtest on 0xArchive's server also replays a trigger in a `book()` handler, against the archive's book every 5 seconds ([Data](https://docs.0xarchive.io/terminal/guides/data.md)), at the book tier:

* the entry meets the next update, never the one that fired it, and walks the opposite side up to the plan's worst price, each level giving at most 96% of its size; what is left does not fill, and a lost book before it cancels the entry;
* a resting entry fills at its price only when a later update's best or a later bar trades through it, never at a touch;
* the stop is seen on the first update at or past it and fills walking that book, never better than the stop; a time stop closes on the first update past it;
* a target fills at its price only when the book or a bar trades through it; when the stop and a target are reached in one update, the stop wins.

Its report says so: `assumptions.fills` is `'book 5s'`, and `replay.bookTier` counts the triggers, fills, stops, targets and time stops, with the largest share of the book any fill took. Trade and liquidation triggers are still not replayed, and are counted in `replay.notReplayed`.

## Next step

Every call, option and size is in the [Algos reference](https://docs.0xarchive.io/terminal/reference/algos.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.