# Portfolios

> Run several assets from one shared pool of capital as a single book: rosters, contention, rebalancing, risk controls and the book report.

Source: https://docs.texttoquant.com/reference/portfolio

---

A **portfolio** runs several assets from **one shared pool of capital** as a single *book*. Each asset
carries its own strategy, and a **contention** rule decides who gets the cash when more than one asset
wants to enter on the same bar. Where a single backtest is one symbol, one strategy and its own
balance, a portfolio is a roster of assets competing for one pool, with a shared equity curve,
per-asset attribution, and a record of every signal that could not be filled.

<Callout variant="info" title="Spot crypto, v1">
  The v1 portfolio path is spot crypto only. Symbols resolve to Binance USDT pairs (BTC becomes
  BTCUSDT). A book holds up to 20 assets.
</Callout>

## Building a book

There are three ways in, and they all land on the same review then run flow. For a step by step
walkthrough see [Build a portfolio](/docs/guides/build-a-portfolio).

| Path | How |
| --- | --- |
| One prompt | Describe every asset in a single sentence. A multi-asset prompt is split into one parsed strategy per asset, and the terminal switches to Portfolio mode for you. |
| Visual builder | Flip the sidebar toggle from **Single asset** to **Portfolio**, open the **Visual** tab, and add assets by hand. Each asset opens the normal strategy builder canvas. |
| API / MCP | `parse_portfolio` then `run_portfolio`, see the [MCP server](/docs/api/mcp). |

```text
Buy BTCUSDT and ETHUSDT on 1d when price closes above the 20 EMA; exit when it closes below. $50k, prorata.
```

Each asset shows up as a review card with the clause the splitter assigned to it ("Split as"), its
parsed logic, and its market. Before you run, you can edit any asset, copy one asset's rules to the
whole roster, or remove it.

## Shared capital & contention

A book trades from one **initial capital** amount, not one balance per asset. The terminal starts a
new book at **$30,000**; the API and MCP default to **$100,000**.

When two or more assets signal an entry on the same bar and there is not enough free cash for all of
them, the **contention** rule resolves the conflict:

| Rule | Behaviour |
| --- | --- |
| `rank` (default) | Priority order wins. Priority follows roster order unless you set it, say "prioritise BTC over ETH" to pin it. |
| `prorata` | The contested cash is split proportionally across the competing assets. |
| `strength` | The strongest signal fills first. Ask for it with "strongest signal fills first". |

A signal that cannot be funded is never dropped silently. It is written to the **skip ledger** with a
reason, see [Reading the book report](#reading-the-book-report).

## Rebalancing

Off by default. You turn it on by naming target weights; percentage stops and take-profits are never
mistaken for weights.

| Setting | Values |
| --- | --- |
| Schedule | daily, weekly, monthly, quarterly, yearly, or **never** (default monthly) |
| Overlay mode | resizes open positions toward the target weights on each boundary, while entry signals still fire |
| Allocation mode | holds the whole book to the target weights with no entry signals, a long-only hold-to-weight book |
| Targets | per-asset weights that sum to 100% or less; a cash remainder is allowed |
| Cash sleeve | state the remainder explicitly: "hold 40% BTC, 30% ETH and 30% cash" |
| Drift band | rebalance off-calendar whenever a sleeve drifts this far from target. Quote it in **percentage points** or in percent: a band is a difference between two percentages, so "5 points off target" and "a 5% drift band" are the same rule |
| Minimum trade | skip corrective trades below a dollar amount, so a tight band stops generating $12 trades |

```text
Rebalance monthly to 40% BTC, 30% ETH, 30% SOL.
Hold 60% BTCUSDT and 40% ETHUSDT, rebalance quarterly.
Allocate 50% BTCUSDT and 50% ETHUSDT once and never rebalance.
Hold 60% BTCUSDT and 40% ETHUSDT, rebalance monthly but skip rebalance trades under $500.
Equal weight BTCUSDT, ETHUSDT and SOLUSDT, rebalance monthly and also whenever a position drifts more than 5 points from its target.
```

<Callout variant="info" title="A longer cadence is the cheapest way to cut rebalance costs">
  Every rebalance is turnover you pay for. Quarterly tracks the targets more loosely than monthly and
  trades about a third as often; **never** allocates once on the first bar and then lets the winners
  run, which is the buy-and-hold baseline every other schedule should be measured against.
</Callout>

### Rotation

Instead of fixed weights, rank the universe each rebalance and hold only the leaders, or the
laggards.

```text
Buy the top 3 by momentum over 30 days, rebalance weekly.
Hold the 2 lowest volatility coins, rebalance monthly.
Rotate into the best 2 by 30-day return and short the worst 2.
```

Rank by momentum, return, rate of change, RSI, Sharpe or volatility. A rotation is always an
allocation book, the held set and the weights are both chosen by rank, so entry signals do not run.

Ranking is **relative**: it answers *which of these is best*, never *is any of them any good*. In a
market where everything is falling, a top-N rotation dutifully buys the best of the losers and stays
fully invested all the way down. Add the absolute test and the book goes to cash instead when
nothing clears it:

```text
Hold the top 2 by 90 day momentum but only while their momentum is positive, rebalance monthly.
Rotate into the top 3 by momentum, cash if none are positive.
```

That second test is what makes it *dual* momentum. The floor gates the LONG side only: it says
which names are worth owning, not which are worth shorting, so a long/short rotation still takes its
short sleeve from the worst-ranked names. When nothing clears the floor the run says so explicitly,
so a book that deliberately sat out a bear market is never mistaken for a broken rotation.

## Risk controls

All optional, and all off unless you ask for them. State them once, anywhere in the prompt, a
control that talks about the whole book never has to be repeated per asset. Percentages that belong
to a leg ("stop loss 3%") are never mistaken for a book knob.

Every control below can be **typed in the prompt** or **edited in the Book settings panel** on the
review page: including the ones whose value is a ladder, a schedule, a list of days or a session
window. Clearing an editor turns that family off completely; there is no "set but inert" state. An
edit you make by hand is authoritative: re-parsing the prompt afterwards will not overwrite it.

### Size and concentration

| Control | Effect | Say |
| --- | --- | --- |
| Book drawdown stop | Flatten the book and halt new entries once equity falls this far below its peak. Deliberately permanent for the rest of the run | "stop the book at 20%" |
| Book take-profit | The profit-side twin: close everything and stop once the account is up past a target | "close the whole book at +30%" |
| Max concurrent positions | Cap how many assets can be open at once | "at most 3 open positions" |
| Total exposure cap | Ceiling on total open notional versus book equity | "cap exposure at 80% total" |
| Per-asset cap | Largest share of the book any one asset can hold | "max 20% per asset" |
| Per-sleeve loss stop | Force-close any position that falls this far below its own entry, whatever the strategy's own exit says. It can re-enter later | "stop each position that falls 12% below its entry" |
| Sector exposure cap | Bound a whole group's share of the book, by naming the group's members | "cap memecoins (DOGEUSDT, SHIBUSDT) at 15%" |
| Positions per sector | Bound how many *names* a group may hold at once | "max 2 positions per sector" |
| Cash reserve | Never spend the book below this much cash | "always keep 20% in cash" |
| Correlation cap | Skip a new entry whose recent returns move too closely with a position already open | "avoid holding two positions correlated above 0.8" |
| Volatility target | Scale every new position so the book's realized volatility tracks a target | "target 15% annualized volatility" |
| Sleeve drawdown halt | Retire an asset for the rest of the run once its own running P&L is this far below its peak | "stop trading any coin that is down 20%" |
| Drawdown-scaled sizing | Make every new position smaller while the book is in a drawdown, restoring full size on recovery | "halve position size while the book is in a 10% drawdown" |
| Open risk budget | Bound the total *distance to stop* × size across every open position, ten positions each risking 1% is a 10% book risk | "never have more than 6% of the book at risk" |
| Correlation clusters | Group everything that moves together and hold at most N names per cluster, the transitive answer where the correlation cap is the pairwise one | "at most one position per correlation cluster" |
| Positions per symbol | On a book with several strategies on the same coin, bound how many of them may be open at once | "only one position per symbol", "BTC gets 3 positions max" |
| Sectors by name | Tag your sectors in plain English once, then cap them by name | "BTC and ETH are majors, DOGE and SHIB are memecoins. Max 60% in majors, at most 2 memecoins" |
| Minimum position size | Skip a fill that clears the venue minimum but is too small to be worth the commission | "skip any position under $1000" |
| One sizing rule | Replace whatever the individual legs said with a single book-wide rule | "risk 1% of the book per trade", "size every position at 10% of equity" |
| Per-position volatility target | Even out the sleeves so a wild coin and a calm one carry comparable risk | "target 20% volatility per position" |
| Top-N concentration cap | Bound what the biggest holdings add up to *together*. Four names at 24% each break this while breaking no single-name limit | "cap the top 3 names at 60% of equity", "no more than 60% of the book in the top 3 holdings" |
| Every sector, one number | Bound each sector without enumerating them, the way a mandate is written. Untagged sleeves are never capped by it | "never put more than half the book in one sector", "max 40% per sector" |
| Liquidity floor | Skip an entry on any bar whose traded value was below a dollar figure, so the book never fills where the tape could not have | "require at least $10m of daily volume", "skip anything trading under $1m" |

<Callout variant="info" title="Two different per-sleeve stops">
  The **per-sleeve loss stop** asks *is this open position underwater*, it closes anything trading
  below its own entry. The **sleeve drawdown halt** asks *has this sleeve given back its gains*, a
  coin that ran up 30% and round-tripped the lot never triggers the first one, because no single
  position was ever deeply below entry. Use the loss stop as a safety net on a leg with no stop of
  its own; use the drawdown halt to stop feeding an asset that has stopped working.
</Callout>

<Callout variant="info" title="A count cap and a dollar cap say different things">
  "Cap memecoins at 15%" and "max 2 positions per sector" are not the same instruction. One oversized
  name can consume a whole notional cap on its own, while two small ones fit comfortably inside it.
  Use the notional cap to bound risk, the count cap to force breadth.
</Callout>

### Timing and pacing

| Control | Effect | Say |
| --- | --- | --- |
| New positions per period | However many signals fire, only this many new positions open per bar, day, week or month. The strongest get the slots | "open at most 1 new position per day" |
| Daily / weekly loss limit | Stop opening for the rest of the period once the book is down this much from the period's opening equity, in percent or in dollars, then resume next period | "stop trading for the day after a 3% loss", "cap the daily loss at $500" |
| Quit while ahead | The profit mirror: stop opening once the period has banked its target. Entry-side: open positions keep managing themselves, so the banked number can still drift | "stop for the day after making $1000", "done for the day once we're up 2%" |
| Re-entry cooldown | After a sleeve closes, that asset waits this many of its own bars before it can be bought again | "no re-entry for 5 bars" |
| Minimum holding period | A signal exit cannot close a position before it has been held this many bars | "hold every position for at least 5 bars" |
| Scheduled flatten | Close every open position on a calendar boundary, then carry on trading | "close everything on Friday", "no positions over the weekend" |
| Trading calendar | Only open positions on these days or months | "only trade Monday to Thursday", "don't trade in December" |
| Quarters | Only open in certain quarters | "only trade in Q4" |
| Days of the month | Sit out particular dates, or the days around month end | "never trade on the 1st", "no trading around month end" |
| Trading hours | Only open inside a UTC window, the intraday version of the calendar | "only trade between 08:00 and 16:00 UTC" |
| One-off blackouts | Sit out a named date range | "don't trade between 2024-12-20 and 2025-01-05" |
| Book warm-up | Let the book settle before it commits capital | "wait 50 bars before trading", "skip the first month" |
| Total trade budget | A hard ceiling on trades for the whole run | "no more than 100 trades in total" |
| Trades per period | The same ceiling, per day, week or month | "no more than 5 trades a week" |
| Entry spacing | Require a gap between any two entries, so the book cannot go on all at once | "leave at least 3 bars between any two entries" |
| Turnover cap | Bound annualized turnover as a share of equity, counting both sides of a round trip | "keep annual turnover under 200%" |
| Breadth confirmation | Fund nothing unless this many sleeves signal on the *same* bar | "only enter when at least 2 assets signal together" |

<Callout variant="info" title="Pacing is not the same as a position cap">
  "At most 3 open positions" bounds how much the book holds. "One new position a day" bounds how fast
  it gets there, a book allowed three names can still be told to leg into them one per day. And the
  daily loss limit is the **recoverable** twin of the book drawdown stop: that one halts for the rest
  of the run, this one sits out the session and starts again tomorrow.
</Callout>

### Direction and regime

| Control | Effect | Say |
| --- | --- | --- |
| Gross long / short caps | Bound each side of a long/short book separately | "max 60% long and max 40% short" |
| Positions per direction | Bound how many *names* each side may hold at once | "at most 2 shorts at a time" |
| Net exposure cap | Bound how far the book may lean either way | "keep net exposure under 20%", or "run it market-neutral" |
| Equity-curve filter | While book equity is below the average of its own recent equity, stop opening, or go fully to cash, and resume when the curve recovers above it | "stop trading when the equity curve drops below its 50-day average" |
| Market regime gate | Nothing in the book opens unless a condition on another symbol holds | "only trade when BTC is above its 200 day EMA" |

A total exposure cap cannot express a market-neutral book: it reads a 100%-long book and a 50/50
long-short book as the same number. The net cap is judged across the whole bar, so a hedged pair is
never refused for being "too long" on its first leg, only a lopsided bar loses its weakest name on
the heavy side.

<Callout variant="warning" title="A minimum hold never gates your stop">
  It defers **signal** exits only, the indicator or comparison rule you wrote as your exit. A stop
  loss, take profit, trailing stop, liquidation and an explicit "exit after N bars" always fire, at
  any age. A minimum hold that swallowed a stop would be a risk, not a risk control.
</Callout>

<Callout variant="warning" title="A scheduled flatten also cuts your winners">
  Closing out every Friday removes weekend gap risk and removes every trend that would have run
  straight through the flush. It is a real trade-off, not a free safety measure, run the same book
  with and without it before keeping it.
</Callout>

<Callout variant="warning" title="An equity-curve filter cuts both ways">
  It damps long drawdowns, and it also guarantees you miss the first leg of every recovery, the
  curve has to climb back above its own average before the book will trade again. Judge it on the
  whole equity curve, not on max drawdown alone.
</Callout>

The regime gate can also act rather than merely pause, and it speaks plain words:

| Instruction | Effect |
| --- | --- |
| "go to cash when BTC drops below its 200 day MA" | A regime break CLOSES what the book holds, not just new entries; positions re-open on their own signals when the regime returns |
| "go to cash in a bear market" | Read as BTC below its 200-day SMA, flattening, both halves flagged as inferred |
| "avoid trading when BTC is dropping fast" | BTC's 7-day rate of change staying above −10%, flagged as inferred |
| "only trade alts when BTC is stable" | BTC's NATR(14) below 5%, flagged as inferred |
| "go to cash when @regime-score drops below 40" | Your own uploaded series decides risk-on or risk-off for the whole book |

<Callout variant="info" title="The regime gate reads closed bars only">
  A daily regime filter on a 4h book reads the last **completed** daily bar, so there is no
  look-ahead. During the warm-up, before the regime series has enough history, the gate is closed
  a filter that has not resolved yet is not permission to trade.
</Callout>

### Entry filters on each leg's own chart

The regime gate above judges ONE symbol for the whole book. These ask the same question of EVERY
chart separately, which is the shape most trader filters take: "only trade when ADX is above 25"
means each leg's own ADX.

| Control | Effect | Say |
| --- | --- | --- |
| Indicator filter | Entries only while an indicator condition holds on that chart | "only trade when ADX is above 25", "only enter when RSI is below 70" |
| Custom filter | The same gate driven by your own uploaded indicator | "only trade when @trend-score is above 70" |
| Price vs its own average | The per-chart trend filter | "only go long when price is above the 200 day moving average" |
| Trend alignment | Longs above the average, shorts below it: one instruction, two mirrored conditions | "only trade in the direction of the 200 EMA" |
| Chop avoidance | ADX above 20, the conventional trend floor, flagged as inferred | "avoid choppy markets" |

<Callout variant="info" title="Blocked entries are ledgered; exits never are">
  The signal still fires and lands in the skip ledger with its own reason, so the report shows how
  much the filter discarded. An open position always exits by its own rules, even when the filter
  has turned against it. And the filter fails CLOSED, a condition that cannot be evaluated blocks
  the entry rather than silently opening the book.
</Callout>

### Sessions and day boundaries

| Control | Effect | Say |
| --- | --- | --- |
| Named sessions | An approximate UTC window (London 08:00-16:00, New York 13:00-21:00, Tokyo 00:00-08:00) flagged as inferred | "only trade the London session" |
| Start every day flat | Flush the book on each UTC day's last bar; re-enter on signals next morning | "start every day flat", "don't hold overnight" |
| Avoid the open / close | The crypto day opens at 00:00 UTC | "avoid the first hour after the open", "no new positions in the last hour" |
| First hours only | The mirror: only the day's opening burst | "only trade the first two hours" |

### Costs and execution

A backtest with no fees and instant fills is a brochure, not a result. Both are stated once for the
whole book.

| Control | Effect | Say |
| --- | --- | --- |
| Fees and slippage | Apply the same cost assumption to every leg | "use 0.1% fees and 0.05% slippage across the book" |
| Fill delay | Model the gap between a signal and a fill by entering N bars later | "assume a 1 bar delay on every entry" |
| Next-open fills | Fill every entry at the NEXT bar's open instead of the signal bar's close, removing a free fill at the very price the signal was computed from | "assume I get filled at the open of the following day" |
| Short borrow | Charge the annual borrow rate as a per-bar carry on every open short. Name the book's timeframe: an annual rate cannot be converted without it, and the run declines rather than guessing | "shorts cost 6% a year to borrow" |
| Cash yield | Credit interest on idle cash, so a defensive book is not silently punished for being defensive | "earn 5% on the cash we are not using" |

<Callout variant="info" title="The two assumptions that flatter a book most">
  A signal computed at a bar's close cannot also be *filled* at that close: **next-open fills**
  remove that free fill, and they will make almost any result worse. That is the point. The mirror
  case is **cash yield**: with idle cash earning exactly 0%, a book that correctly sat out a bad
  quarter still loses to a benchmark that compounded through it, and the comparison says nothing
  about the strategy. Turn both on before you compare two books.
</Callout>

<Callout variant="warning" title="Zero fees is a choice, not a default you can ignore">
  A frictionless run draws an honesty flag for a reason: at a few trades a month it barely matters,
  and at several a day it is the difference between a strategy and a fee generator. Run your real
  cost assumption before you believe a high-turnover book.
</Callout>

### One stop and one target for every position

The most-used trader instruction of all, stated once for the whole book instead of repeated on
every leg. An exit is added only where the leg does not already carry one of that type, the leg's
own rule wins, and the result reports which legs took the book's exits and which kept their own.

| Control | Effect | Say |
| --- | --- | --- |
| Book stop | A percentage stop on every position | "put a 2% stop on every position" |
| Book target | A percentage target on every position | "take profit at 8% on every trade" |
| Book trailing stop | Trail each position behind its own peak | "trail every position 3%" |
| ATR stop | A volatility-sized stop, a calm coin and a wild one get different distances from the same instruction | "use a 1.5 ATR stop on every position" |
| ATR trail | The trailing version of the same idea | "trail every position with a 2 ATR stop" |
| Indicator exit | Close each position when its own chart says so | "close every position when RSI is above 80" |
| Time stop | Close a position that has run out of time rather than price. Lowest priority: a trade that worked has already left by its own exit | "time stop after 20 bars", "exit if the trade has not worked in 10 bars" |
| Max holding time | The calendar phrasing of the time stop, the ceiling that pairs with the minimum-hold floor | "only hold trades for a maximum of 5 days" |

<Callout variant="warning" title="Book exits need book scope">
  "Put a 2% stop **on every position**" is a book instruction; a bare "stop loss 2%" belongs to the
  leg that says it. That anchoring is what lets both live in one prompt without stealing from each
  other. An ATR exit on a series where ATR genuinely cannot be computed is refused and reported,
  never silently added, a stop the engine can never fire would leave the position unprotected
  while the config says otherwise.
</Callout>

### The risk unit

Traders think in R: what a trade risks, and what it pays if it works.

| Control | Effect | Say |
| --- | --- | --- |
| Reward-to-risk | Every leg with a percentage stop gets a target at stop × R | "risk 1% per trade with a 2 to 1 reward to risk", "target 2R on every trade" |
| Fixed dollar risk | Size so a stop-out costs a stated amount. Does NOT compound, that is the point of stating it in dollars | "size every position so it risks $500" |
| Per-trade ceiling | A cap, not a sizing rule: a leg asking for less keeps its size, one asking for more is trimmed | "never let one trade lose more than 1% of the account" |
| Portfolio heat | The open-risk budget, under the name traders use: distance-to-stop × size summed across open positions | "keep total heat under 6%" |
| Risk in words | Sub-1% risk written the way people write it | "risk half a percent per trade" |

<Callout variant="info" title="An R target that cannot compile is reported, never skipped silently">
  R is a ratio, so it needs a percentage stop to multiply. A leg with an ATR or swing stop, or one
  that already states its own target, is listed in the result as skipped, "target 2R" that quietly
  did nothing on half the book is worse than an error.
</Callout>

### Scaling out

| Control | Effect | Say |
| --- | --- | --- |
| Ladder | Take profit in pieces; whatever the rungs don't add up to keeps running | "sell a third at 5%, a third at 10%, and hold the rest" |
| R-denominated rung | A rung at a multiple of the leg's own stop | "scale out half at 2R and let the rest run" |
| Partial then full | Half at the first level, everything left at the second | "partial exit at 5% and full exit at 10%" |

Sizes are shares of the ORIGINAL position: "a third, then a third" really closes a third each
time, with the remaining-quantity arithmetic handled for you. Slices appear on the closed trade as
partial exits, the same shape the single-asset report already renders.

### Pyramiding discipline

| Control | Effect | Say |
| --- | --- | --- |
| Never average down | Refuse any add while the position is at or below its blended entry | "add to winners only", "never average down" |
| Pyramid cap | Total entries per position, the opener included | "pyramid up to 3 entries" |
| …counted in adds | The same cap stated the way people actually say it. **Two adds is three entries**, because the opener counts | "add to a winner at most twice" |

A refused add leaves the position exactly as it was and is counted in the result, a disciplined
book shows what it declined rather than looking like the adds never fired.

### Protecting a profit

| Control | Effect | Say |
| --- | --- | --- |
| Book profit lock | A ratchet: once the book is up past a trigger, defend a floor. The floor only ever moves up, so a give-back cannot unwind a lock already earned | "once the book is up 20%, never let it go below breakeven" |
| One-number profit lock | The same ratchet with only the trigger stated, the floor is inferred at half the trigger, and the result says so | "lock in profits above 20%", "protect gains once up 15%" |
| Give-back fraction | The book trailing stop as a fraction of the peak | "give back no more than a third of the peak" |
| Book trailing stop | Trail the whole book a fixed distance behind its own peak | "trail the book 10% off its peak" |
| Stop management | Move every position's stop to breakeven, or start trailing it, once it is far enough in profit | "move every stop to breakeven after +5%" |

### Sizing that responds to something

All of these are multipliers on the size you asked for, and they compose: with a ceiling, so several
cannot conspire into a position nobody asked for.

| Control | Effect | Say |
| --- | --- | --- |
| Size by recent record | Cut size after a losing streak, press after a winning one | "halve size after two losses", "double size after two winning trades" |
| Ease into full size | Ramp from a starting exposure to full size over a period | "start at 25% exposure and scale to 100% over 3 months" |
| Kelly fraction | Size from the book's own measured edge | "use half Kelly sizing" |
| Market-state rules | A multiplier gated on a condition, judged on each leg's own chart at entry | "halve size when ADX is below 20", "reduce size in high volatility" |
| Custom conviction | The same rule driven by your own uploaded indicator | "double size when @conviction is above 80" |

<Callout variant="warning" title="Kelly on five trades is noise wearing a formula">
  The Kelly size is computed from the book's own closed trades and stays inert below 20 of them. A
  bare "Kelly sizing" is read as **half** Kelly and the result says so, full Kelly is far too
  volatile to run, and a measured edge that turns out to be negative sizes to zero rather than short.
</Callout>

### Circuit breakers

The count-based cousins of the daily loss limit: that one counts percent lost, these count events.

| Control | Effect | Say |
| --- | --- | --- |
| Losing-streak halt | Stop opening for the rest of the run after a run of losing trades | "stop the book after 5 consecutive losing trades" |
| Losing-streak pause | Stand down for a number of bars, then come back | "pause for 5 bars after 2 losses" |
| Done for the day | Stand down for the rest of the day, week or month | "stop trading for the rest of the day after 2 losses" |
| Losing periods | Stop after a run of losing days, weeks or months | "stop trading after 3 losing days in a row", "halt after 3 losing weeks" |
| Performance floor | Stop once the measured win rate or profit factor falls through a floor | "stop if the win rate drops below 40%" |
| Rolling Sharpe floor | Stop opening while the *risk-adjusted* record is under a floor, the drawdown-blind twin of an equity-curve filter. State no window and it is read over the last 60 equity points, and the run tells you it assumed that | "stop trading if the rolling sharpe drops below 0.5", "stop opening while our 90-day Sharpe is negative" |
| Behind the benchmark | A RELATIVE drawdown stop: the book can be UP and still trip this, because what it measures is the gap to buy-and-hold | "benchmark against BTCUSDT buy and hold and stop trading if we underperform the benchmark by 15%" |

<Callout variant="info" title="A breaker stops you opening, it never liquidates">
  "Stop trading" means take no new risk. Anything already open still manages itself by its own exits,
  because closing a book at whatever the market happens to be showing is a different instruction from
  the one you gave. A performance floor is also inert below 20 closed trades: halting a book on noise
  is worse than not halting it.
</Callout>

### Aiming a control at one coin

Almost every control above can name a single asset instead of the whole book. The more specific
statement always wins.

```text
Cap BTCUSDT at 40% and ETHUSDT at 20%.
Risk 2% on BTCUSDT and 1% on ETHUSDT.
Stop BTCUSDT at 10% and cut ETHUSDT if it's down 8%.
Hold ETHUSDT for at least 5 bars.
Wait 10 bars before re-entering BTCUSDT.
```

<Callout variant="warning" title="A per-coin control must name a coin the book holds">
  Aiming a cap at an asset that is not in the portfolio is refused rather than quietly dropped, a
  cap that silently is not there reads exactly like a cap that is.
</Callout>

## Custom indicators at the book level

Anywhere a built-in indicator works in a book-level control, a custom (`@`-named) indicator you
have uploaded works too. The series is loaded per leg through the same resolver a leg's own custom
condition uses, so an unknown name fails the run loudly instead of gating on a column that is not
there.

| Surface | Say |
| --- | --- |
| Entry gate | "only trade when @trend-score is above 70" |
| Position sizing | "double size when @conviction is above 80" |
| Book regime, and go to cash on it | "go to cash when @regime-score drops below 40" |
| Rotation ranking | "hold the top 2 by @score, rebalance weekly" |
| Position exits | "close everything when @exit-score is above 80" |

<Callout variant="info" title="Two failure postures, on purpose">
  An entry **filter** that cannot be evaluated blocks the entry, a gate that is not there must not
  silently open the book. A sizing **rule** that cannot be evaluated leaves the size alone, a rule
  that is not there must not zero a position the strategy asked for. One is permission, the other
  is adjustment, and each fails in the direction that does less damage.
</Callout>

## Capital deployment and signal selection

| Instruction | Effect |
| --- | --- |
| "only take the strongest signal each day" | Strength contention picks WHICH signal wins the capital; a one-per-day allowance decides HOW OFTEN |
| "rank the signals and take the best two" | When five sleeves fire together, the two best-ranked open and the rest are ledgered |
| "trade with 50% of capital and keep the rest in reserve" | The cash-reserve floor, stated from the deployed side |
| "deploy capital gradually over the first month" | An exposure ramp from 0% to 100% of the stated size, flagged as inferred |
| "withdraw profits monthly" | ALL of the gain out each month: flagged as inferred, because it turns off compounding and you should see that stated |

## What the book is measured against

Every portfolio is compared to a **benchmark**, and by default that is an equal-weight buy-and-hold
of the book's own constituents. That is often not the comparison you meant, so you can say which one
you want.

| Benchmark | Say |
| --- | --- |
| Buy and hold one asset, including one the book does not trade | "compare it against BTC buy and hold" |
| A stated mix, bought once and left to drift | "compare to a 60/40 BTC ETH portfolio" |
| The same mix, systematically rebalanced | "compare to a 60/40 BTC ETH portfolio rebalanced monthly" |
| Cash, a flat line at your starting capital | "compare against sitting in cash" |

Whatever you pick becomes the denominator for the numbers that actually answer "was this worth it":
outperformance, beta, correlation, annualized alpha, tracking error, information ratio, and up/down
capture. Add "assume a 4% risk free rate" and the alpha is measured over that hurdle instead of zero.

<Callout variant="info" title="A blank statistic means unmeasurable, not zero">
  Beta against cash is not zero, it is undefined, a flat benchmark has no variance to regress
  against. The report leaves those fields empty rather than printing a number it cannot justify.
  Against cash, the outperformance figure is the whole story.
</Callout>

<Callout variant="warning" title="A rebalanced benchmark is a harder bar">
  "A 60/40 portfolio" normally means one bought 60/40 and left alone, so that is the default. Saying
  "rebalanced monthly" makes it systematically rebalanced, which harvests the mean reversion between
  its legs and is materially harder to beat in a choppy market.
</Callout>

## Deposits and withdrawals

A book can also move money across the **account boundary** on a schedule: pay in every month, or
take a share of the profits out.

| Instruction | Effect |
| --- | --- |
| "add $1000 to the account every month" | Cash is added to the pool; the strategy then puts it to work on its own signals |
| "withdraw 20% of profits every month" | A share of the gain above the starting capital is taken out |
| "take out $2000 each month" | A fixed amount is taken out |

This is **not** the same instruction as DCA. Dollar-cost averaging deploys the pool the book already
has, buying a fixed amount on a schedule. A deposit grows the pool and leaves the timing to your
rules. A withdrawal only ever comes out of cash the book is actually holding, it never sells a
position to fund one, and a profit share never touches the starting capital.

<Callout variant="warning" title="Deposits flatter the raw return">
  Money you paid in raises the final equity without the strategy earning a cent, so a plain "total
  return %" on a contributed book is not a performance figure. The report carries a
  **contribution-adjusted return** measured against the money actually put in, with withdrawals
  added back, that is the number to judge the book on. It is reported for **every** cash flow,
  however you phrased it.
</Callout>

<Callout variant="info" title="State each flow once">
  A book may state each cash flow **once**. "Withdraw $1,000 a month" is one instruction; saying it
  twice, once in prose and again as an explicit setting, used to make the book pay out twice, so
  an overlapping pair is now refused by name rather than applied silently. Deposits, fixed
  withdrawals and profit shares are each independent, so a book can state all three.

  A withdrawal and a profit share are each carried by their own setting at **every** cadence
  monthly, quarterly or yearly, so the same instruction never lands on a different key depending on
  how often it repeats. A **deposit** is the one flow with a single home, since dollar-cost averaging
  is a different instruction: it deploys the pool the book already has rather than growing it.
</Callout>

## Cross-asset conditions

One asset's entry can depend on another. Gate a trade on a second symbol and the splitter attaches it
as a cross-asset condition on the dependent asset.

```text
When BTC RSI crosses above 65 on 1d, if ETH RSI is above 50, buy ETHUSDT.
```

<Callout variant="warning" title="Entry gating is live, exit gating is not">
  A cross-asset condition that gates an **entry** runs at execution time. Cross-asset **exit**
  conditions are not live yet: a book that relies on one is refused before it runs, with the note
  "aux signal exit: pending parity". Rewrite the exit as a stop, a target, or a same-asset condition.
</Callout>

## Reading the book report

A portfolio report shows what a single backtest cannot.

- **Book equity versus its benchmark.** The shared equity curve plotted against the benchmark, with a
  beating or trailing readout. The benchmark is an equal-weight buy and hold of the same assets
  unless you asked for another one; see [What the book is measured against](#what-the-book-is-measured-against)
  and it carries the relative numbers alongside it: outperformance, beta, correlation, alpha,
  tracking error, information ratio, and up/down capture.
- **Per-asset results.** Either full per-asset panels (grade, equity, trades) or a **Per-asset
  attribution** table (realized P&L, trades, win rate, exposure). Per-asset return and Sharpe are
  drawn on an equal-base display axis, a slice of the pool, not standalone capital.
- **Skip ledger.** Every entry signal that did not fill, with its reason. Read it before concluding
  a strategy did nothing: a book that suddenly trades less is usually a control doing its job.

  | Reason | What happened |
  | --- | --- |
  | `NO_CAPITAL` | the signal fired but the shared pool had no cash left for it |
  | `BELOW_MIN_NOTIONAL` | the affordable size was under the exchange minimum |
  | `GUARD` | a per-asset gate blocked it (session window, time filter, trading guard) |
  | `PRE_LISTING` / `STALE_BAR` | the asset had not listed yet, or had no bar closing on that tick |
  | `PER_ASSET_CAP` / `MAX_CONCURRENT` / `EXPOSURE_CAP` | a size or count cap was already reached |
  | `CORRELATION_LIMIT` | its returns were too correlated with a position already open |
  | `GROUP_CAP` / `GROUP_POSITION_CAP` | its sector was at its exposure cap, or already held its maximum number of names |
  | `TRADE_THROTTLE` | the book had already opened its allowance of new positions for the period |
  | `LOSS_LIMIT_PAUSE` | the book was paused for the rest of the period after hitting its loss limit |
  | `REENTRY_COOLDOWN` | the sleeve was still inside its post-exit cooldown |
  | `LONG_EXPOSURE_CAP` / `SHORT_EXPOSURE_CAP` / `NET_EXPOSURE_CAP` | it would have pushed exposure past a directional cap |
  | `CASH_RESERVE` | funding it would have spent the book below its cash reserve floor |
  | `EQUITY_CURVE_FILTER` | the book's own equity was below its moving average, so it was standing down |
  | `REGIME_OFF` | the book-wide regime filter was risk-off on that bar |
  | `DIRECTION_MAX_POSITIONS` | the book was already holding its maximum number of positions on that side |
  | `SLEEVE_HALTED` | that asset was retired for the rest of the run by the sleeve drawdown halt |
  | `BOOK_TIME_FILTER` / `DATE_BLACKOUT` | the book does not open on that day, hour or month, or the date fell inside a blackout window |
  | `DIRECTION_BIAS` | the book is restricted to one side and that signal was on the other |
  | `WARMUP` | the book was still inside its settle period |
  | `TRADE_BUDGET` / `TRADE_PERIOD_BUDGET` | the trade allowance for the run, or for this period, was spent |
  | `ENTRY_SPACING` | it came too soon after the previous entry |
  | `TURNOVER_CAP` | annualized turnover was already at its ceiling |
  | `CONFIRMATION` | fewer sleeves signalled together than the book requires |
  | `BELOW_BOOK_MIN_NOTIONAL` | it would have been smaller than the book's own minimum position size |
  | `OPEN_RISK_CAP` | funding it would have pushed total open risk past the book's budget |
  | `GROUP_MAX_POSITIONS` | its sector/group was already holding its maximum number of positions |
  | `ASSET_TRADE_THROTTLE` | that sleeve had already opened its allowance of positions for the period |
  | `PER_ASSET_NOTIONAL_CAP` | the sleeve was already at its dollar cap for open notional |
  | `PERIOD_LOSS_LIMIT` | the book was stopped for the rest of the period after hitting its loss limit |
  | `LOSS_STREAK_PAUSE` | the loss-streak breaker had paused new entries |
  | `DAILY_GIVEBACK_STOP` | the book had given back too much from the day's peak and stopped for the day |
  | `OUTSIDE_TRADING_DAYS` | the bar fell outside the weekdays the book may open on |
  | `OUTSIDE_TRADING_HOURS` | the bar fell outside the session window the book may open in |
  | `BLACKOUT_DATE` | the bar fell inside a date range the book sits out |
  | `DIRECTION_NOT_ALLOWED` | the book is restricted to the other side (long-only or short-only) |
  | `BELOW_MIN_VOLUME` | the bar traded less value than the book's liquidity floor |
  | `BELOW_MIN_TRADE_NOTIONAL` | the final size, after every cap, was under the book's minimum trade size |
  | `BELOW_MIN_SIGNAL_MARGIN` | the signal did not clear its own threshold by the required margin |
  | `BENCHMARK_LAGGING` | the asset had not outperformed the benchmark over the lookback |
  | `INSUFFICIENT_HISTORY` | the asset did not yet have the minimum bars of history behind it |
  | `ROLLING_SHARPE_FLOOR` | the book's rolling Sharpe was below its floor |
  | `TOP_N_CONCENTRATION` | the fill would have pushed the largest N positions past their combined cap |
  | `ASSET_RETIRED` | the sleeve had lost too much and stopped taking new signals |
  | `WIN_STREAK_BREAKER` | the book was standing down after a run of winning trades |
  | `ASSET_ATTEMPT_CAP` | that coin had already used up its allowance of entry attempts |
  | `OPEN_LOSS_GATE` | a position the book already held was too far underwater to take new risk |
  | `DRAWDOWN_PAUSE` | the book was too far below its high to open anything new, and had not recovered yet |
  | `BIG_LOSS_COOLOFF` | the book was cooling off after a single trade lost more than its limit |
  | `BAR_ENTRY_CAP` | the book had already opened its allowance of positions on that bar |
  | `SIGNAL_EXPIRED` | a signal the book could not fund waited too long and was dropped |
  | `SIGNAL_NOT_HELD` | the entry condition fired but had not held for long enough |
  | `SIGNAL_TOO_WEAK` | it cleared its own threshold by less than the other signals on that bar |
  | `BETA_CAP` | the book's beta to its benchmark was already at its ceiling |
  | `BELOW_MIN_PRICE` | the asset was trading below the minimum price the book will touch |
  | `VOLATILITY_STANDDOWN` | the book's own realised volatility was above its ceiling |
  | `CLUSTER_CAP` / `SYMBOL_POSITION_CAP` | its correlation cluster, or that symbol, was already full |
  | `LOSS_STREAK_BREAKER` / `LOSING_PERIOD_BREAKER` | the book was standing down after a run of losing trades, or of losing days |
  | `PERFORMANCE_FLOOR` | the book was halted because its win rate or profit factor fell through its floor |
  | `ENTRY_FILTER` | the book's entry filter was not satisfied on that leg's own chart |
  | `PROFIT_LIMIT_PAUSE` | the book had banked its profit target for the period and was done trading it |

  Some controls act on **exits** or on the account rather than on an entry signal, so they never
  appear in this ledger. They are counted separately, under the book's risk controls: a minimum hold
  deferring an exit, a scheduled flatten, a sleeve being retired, positions being sized down or up by
  a responsive or market-state sizing rule, a position resized to a stated dollar risk or trimmed to
  its per-trade ceiling, an R target added to a leg, a time stop firing, an add refused by the
  pyramiding discipline, the book sent to cash by a regime break, a delayed fill, the book's profit
  floor closing it out, and each deposit or withdrawal.

  Three entries in that ledger are **not** a control turning a signal away, and the report marks
  them as such: `ENTRY_FILTER_UNREADABLE` means the filter could not be *measured* on that asset
  it did not fail the filter; `NO_NEXT_BAR` means the signal fired on the last bar of the data and a
  next-open fill needs a bar after it; and `LIMIT_NOT_FILLED` means the bar never traded through
  your limit, an execution miss rather than a rule. No fill is ever invented to hide one.
- **Run warnings.** What the engine decided, said out loud. Three of them mean the run's numbers are
  **not comparable with a default run** and are styled to say so: `BOOK_COSTS_APPLIED` (one cost
  model replaced every leg's own), `FILL_TIMING_NEXT_OPEN` (entries filled at the next open, not the
  signal close) and `COMPOUNDING_OFF` (every position sized off the starting capital). `AVERAGED_DOWN`
  is styled as a warning of its own, it is the only family that adds risk to a losing position.
- **Money that moved outside trading.** Withdrawals, profit shares and interest on idle cash all
  change what final equity means, so the header names them beside it. A *shortfall* count means a
  scheduled payout could not be funded in full.
- **Retired sleeves.** If the sleeve-retirement rule fired, the report names which asset the book
  gave up on and what it cost, usually the most actionable line in the whole thing.
- **Book grade.** One letter grade for the whole book, from the same grader as a single backtest,
  alongside book-level Monte Carlo and walk-forward robustness.

You can **share** a book as a read-only `/sp/<token>` page, and export the merged trades as CSV or the
full result as JSON.

## Known limits

These are behaviours that are *correct* but surprising, the ones most likely to make a run look
broken when it is doing exactly what it was told.

- **Spacing between entries is measured across bars, not within one.** "Leave 3 bars between any two
  entries" stops the book opening on bar 3 and again on bar 4. It does **not** stop three sleeves
  that all signal on the *same* bar from all opening at once, zero bars have passed between them.
  To bound a single bar's burst, say "at most one new position per bar"; the two rules compose.
- **A total trade budget can finish slightly over.** "No more than 100 trades" stops the book
  *opening* once 100 have booked, but positions already open still run to their own exits and book
  as they go. The overshoot is at most the number of positions open when the budget was reached;
  cap concurrent positions (or new positions per bar) to bound it.
- **"Require the signal to hold for N bars" cannot be met by a crossover.** A crossover is true on
  exactly one bar by construction, so asking it to hold for two blocks every entry and the book
  takes no trades. That is the rule being unsatisfiable, not an absence of edge, the run says so in
  its warnings. Use a threshold-style entry ("RSI is below 30") for a signal that can persist.
- **A stand-down phrasing names the side you sit out.** "Stop trading when the equity curve drops
  below its 50-day average" means *trade while above it*. Both readings are supported, "only trade
  while equity is below its average" is a valid, if unusual, instruction, so the wording decides,
  and the report echoes the side the book actually traded on.
- **A rotation that holds cash is the momentum floor working.** "Only while their momentum is
  positive" is an instruction to sit out, so a flat stretch with no trades in a falling market is
  the control doing its job, not a rotation that failed to rank. The run distinguishes the two: one
  warning says nothing cleared the floor, a different one says nothing could be ranked at all.
- **A minimum number of holdings cannot be honoured.** "Always hold at least 3 positions" is
  declined out loud. A signal-driven book can decline exposure but never manufacture it: there is
  no entry to take when nothing has signalled. State it as an allocation instead ("equal weight the
  book, rebalance monthly"), which holds targets rather than waiting for signals.
- **A liquidity floor filters bars; it does not pick names.** "Require $10m of daily volume" skips
  entries on thin bars for the assets you named. Choosing *which* assets to trade by liquidity or
  market cap is refused: it needs a point-in-time constituent list, and ranking today's names over
  history would put survivorship bias into every bar.
- **Spot crypto only** in v1; equities are rejected. A book holds up to **20 assets**.
- **Mixed timeframes are allowed.** The book merges assets on absolute close time and reports the
  equity curve on the coarsest timeframe in the roster.
- Per-asset return and Sharpe use an **equal-base display axis**, not standalone capital. Win rate,
  profit factor and trade counts are each asset's real numbers.
- If the book hits the equity floor it **halts early** and flags ruin.
- Futures books are not supported yet.
- Starting capital stated in the prompt ("a $250k book") is carried into the run. If you drive the
  API or the MCP tools yourself, pass the `settings` object `parse_portfolio` returns straight to
  `run_portfolio`, it holds every book-level control the prompt stated, and a book run without it
  is a book without your risk limits.

Next: [Build a portfolio](/docs/guides/build-a-portfolio) step by step, or the
[MCP tools](/docs/api/mcp) to run one programmatically.
