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

# Routing

> How SORY splits an order, and how to read its reasoning.

**SORY allocates on fee-inclusive prices.** The venue with the worse quoted price is often the better one after fees, and that difference is the whole point.

```text theme={"dark"}
venue A:  ask 100.00, taker 20 bps  ->  all-in 100.200
venue B:  ask 100.05, taker  1 bp   ->  all-in 100.060   <- better
```

## Four things it will not do

<Columns cols={2}>
  <Card title="Allocate through the bound" icon="octagon-x">
    Your limit price, or `max_slippage_bps` from the touch.
  </Card>

  <Card title="Send a slice a venue rejects" icon="ruler">
    Below minimum amount, minimum notional, or lot size.
  </Card>

  <Card title="Drop a remainder silently" icon="scale">
    `requested == allocated + unallocated`, always, with a reason.
  </Card>

  <Card title="Route to an unhealthy venue" icon="wifi-off">
    `Stale` or `Offline` is excluded, and named.
  </Card>
</Columns>

## Price bounds are mandatory

```python theme={"dark"}
await sory.place(symbol="BTC/USDT", side="buy", qty=Decimal("2"),
                 type="market", max_slippage_bps=15)
```

Sweeping to arbitrary depth is not acceptable, and SORY will not invent a bound on your behalf — that is a risk decision.

Your limit price is a **raw** venue price, as you would type it. The router converts it into each venue's all-in terms before comparing.

<Warning>
  `max_slippage_bps` bounds the **allocation**, not the venue-side execution. SORY only allocates against levels inside the bound, but a market order carries no price to the venue, so a book that moves between planning and arrival can still fill through. If you need a hard guarantee, send a limit order at your bound.
</Warning>

## Sub-minimum slices are re-routed

If a venue's share lands below its minimum, that venue is dropped and the walk **re-runs without it**, so the quantity moves somewhere rather than evaporating. Anything genuinely unplaceable comes back as `unallocated`, with a reason.

## Reading a plan

```python theme={"dark"}
plan = sory.simulate(symbol="BTC/USDT", side="buy",
                     qty=Decimal("2"), max_slippage_bps=15)
```

| Field                | Meaning                                                      |
| -------------------- | ------------------------------------------------------------ |
| `requested`          | what you asked for                                           |
| `allocated`          | what was placeable                                           |
| `unallocated`        | the remainder — never silently dropped                       |
| `unallocated_reason` | why, in words                                                |
| `expected_price`     | all-in VWAP across the allocations                           |
| `considered`         | every venue that was looked at                               |
| `excluded`           | `{venue: reason}` for each one not used                      |
| `allocations`        | `venue`, `symbol`, `amount`, `limit_price`, `expected_price` |

`simulate` runs the whole path and sends nothing.

## Expected versus achieved

```python theme={"dark"}
order.expected_price   # what the plan said
order.achieved_price   # fill-weighted average of what the venues reported
```

Slippage against expectation is the number that tells you whether routing is earning its keep. Track it.

## Why a venue was excluded

| Reason                                        | Cause                                     |
| --------------------------------------------- | ----------------------------------------- |
| `stale: no update in 2000ms`                  | the venue went quiet                      |
| `symbol not listed on this venue`             | not tradable there                        |
| `no credentials configured; market data only` | cannot send orders                        |
| `does not declare spot_margin_short`          | `margin=True` where borrowing is unproven |
| `not in the requested venue list`             | you passed `venues=[...]`                 |
| `slice X below the venue minimum Y`           | re-routed elsewhere                       |
