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

# Spread realizável por profundidade

> Spread efetivo por faixa de volume, calculado por VWAP sobre o livro de ofertas

## O problema

O spread de uma oportunidade é calculado sobre o **topo do livro** — o melhor
preço de compra contra o melhor preço de venda:

```
spread = (melhor_bid - melhor_ask) / melhor_ask * 100
```

Isso responde "qual o spread?" mas não responde "quanto capital cabe nele?".
Um spread de 3% que só existe em 40 USD de liquidez é ruído: na segunda unidade
comprada o preço já subiu e o spread evaporou.

## O cálculo

O **spread realizável** consome o livro de ofertas até preencher um alvo de
volume e calcula o preço médio ponderado (VWAP) efetivamente pago e recebido:

* na exchange de **compra**, consumimos os **asks** (é contra eles que se compra);
* na exchange de **venda**, consumimos os **bids**.

```
vwapBuy  = notional_preenchido_compra / quantidade_base_compra
vwapSell = notional_preenchido_venda  / quantidade_base_venda

effectiveSpread = (vwapSell - vwapBuy) / vwapBuy * 100
```

O resultado sai no formato `3.1% @ $1k · 1.9% @ $5k · 0.8% @ $10k`: o mesmo par,
avaliado em três tamanhos de operação.

## O campo `depth`

Oportunidades spot podem trazer um array `depth`, uma entrada por faixa:

```json theme={null}
{
  "ticker": "ZEC-USDT",
  "profit_percent_ask_bid": 3.42,
  "depth": [
    {
      "notional": 1000,
      "vwapBuy": 31.61,
      "vwapSell": 32.59,
      "effectiveSpread": 3.10,
      "insufficientLiquidity": false,
      "filledNotionalBuy": 1000,
      "filledNotionalSell": 1000,
      "bookExhaustedBuy": false,
      "bookExhaustedSell": false
    },
    {
      "notional": 10000,
      "vwapBuy": 31.94,
      "vwapSell": 32.19,
      "effectiveSpread": 0.78,
      "insufficientLiquidity": true,
      "filledNotionalBuy": 10000,
      "filledNotionalSell": 2340,
      "bookExhaustedBuy": false,
      "bookExhaustedSell": true
    }
  ]
}
```

| Campo                                      | Significado                                              |
| ------------------------------------------ | -------------------------------------------------------- |
| `notional`                                 | Alvo da faixa, em USD                                    |
| `vwapBuy` / `vwapSell`                     | Preço médio ponderado de cada perna, em USD              |
| `effectiveSpread`                          | Spread percentual entre os dois VWAP                     |
| `insufficientLiquidity`                    | Pelo menos uma perna não preencheu o alvo                |
| `filledNotionalBuy` / `filledNotionalSell` | Quanto de fato coube em cada perna, em USD               |
| `bookExhaustedBuy` / `bookExhaustedSell`   | O book acabou porque a exchange não publicou mais níveis |

<Callout title="Leia o spread de uma faixa insuficiente com cuidado" icon="alert-triangle" color="amber">
  Quando `insufficientLiquidity` é `true`, o `effectiveSpread` é o spread da
  liquidez que **existia** — não do notional pedido. No exemplo acima, os `0.78%`
  referem-se aos $2.340 que couberam, não aos $10.000. É por isso que
  `filledNotional*` existe: sem ele, o número parece uma promessa que o livro não
  sustenta.
</Callout>

## Quando `depth` não vem

O campo é **opcional** e some do payload quando não pôde ser calculado. Todo
consumidor precisa tratar a ausência. Os motivos legítimos:

* **Fora do top-N.** A listagem (`GET /v1/arbitrage`) só enriquece as `N`
  oportunidades de maior spread bruto — buscar o book de todas a cada tick
  multiplicaria o custo de rede. O detalhe (`GET /v1/arbitrage/{id}`) sempre
  calcula.
* **Cotação não suportada.** Pares cotados fora de USD/stablecoins, BRL e EUR
  não têm como ter a faixa convertida.
* **Book indisponível.** Falha ou timeout na consulta à exchange.
* **Streaming.** O canal SSE `GET /v1/realtime/arbitrage` publica direto do
  motor de comparação e **não** passa pelo enriquecimento, então oportunidades
  vindas por ele nunca trazem `depth`.
* **Kill switch.** `DEPTH_ENABLED=false`.

## Moedas e conversão

As faixas são definidas em **USD** para serem comparáveis entre pares. Como o
livro está na moeda de cotação, é a **faixa** que é convertida (`alvo = faixa ×
taxa`), não os preços nível a nível — e o VWAP volta para USD no final.

* `USD`, `USDT`, `USDC`, `BUSD`, `DAI`, `TUSD`, `FDUSD` são tratadas como
  paridade 1:1 com o dólar. Um depeg de 0,3% desloca a faixa de "$10k" para ~$9.970 de profundidade real; como o spread é uma razão, o número de manchete
  não muda.
* `BRL` e `EUR` usam a cotação do serviço de câmbio. Ele não atualiza em fins de
  semana nem fora do horário comercial, então num boot frio de sábado pares
  nessas moedas podem ficar sem `depth`.

<Callout title="O spread não depende da taxa de câmbio" icon="info" color="blue">
  A taxa se cancela na razão entre os dois VWAP. Uma cotação desatualizada só
  desloca **qual fatia** do livro é amostrada — nunca distorce o `effectiveSpread`.
</Callout>

## Profundidade publicada por exchange

O `insufficientLiquidity` reflete o book **que a exchange devolveu**. Algumas
truncam:

| Exchange                                                                                                | Níveis por lado            |
| ------------------------------------------------------------------------------------------------------- | -------------------------- |
| Binance, Bybit, OKX, Gate.io, Bitget, KuCoin, Kraken, Crypto.com, BingX, HTX, Bitfinex, Mercado Bitcoin | 100                        |
| Coinbase                                                                                                | \~50 (agregado, `level=2`) |
| MEXC, Bitnuvem                                                                                          | 50                         |
| Bitstamp, BitMEX                                                                                        | livro completo             |

Quando o alvo não é atingido por esse limite e não por falta de liquidez real,
`bookExhausted` vem `true` — a interface mostra "book truncado pela exchange"
em vez de "liquidez insuficiente".

## Configuração

| Variável                  | Padrão            | Descrição                                                                                                                                                           |
| ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEPTH_BANDS_USD`         | `1000,5000,10000` | Faixas em USD, separadas por vírgula. São ordenadas, deduplicadas e limitadas a 8. Uma lista inválida cai no padrão em vez de desligar a feature.                   |
| `DEPTH_TOP_N`             | `5`               | Oportunidades enriquecidas na listagem. `0` desliga só a listagem.                                                                                                  |
| `DEPTH_ENABLED`           | `true`            | Kill switch global (listagem e detalhe).                                                                                                                            |
| `DEPTH_ENRICH_TIMEOUT_MS` | `1500`            | Orçamento de latência por request. Faixas que não ficam prontas a tempo são omitidas; o cálculo termina em background e o poll seguinte já encontra o cache quente. |

## Custo de rede

Cada oportunidade exige **dois** livros (a perna de compra e a de venda). Três
camadas mantêm isso barato:

1. **Corte top-N** — de até 25 linhas para 5 na listagem.
2. **Cache de profundidade (20s)** — absorve o poll de 10s do front-end, com
   *singleflight* colapsando requisições concorrentes idênticas.
3. **Cache de book (4min)** — um dado par em uma dada exchange custa no máximo
   uma ida à rede a cada 4 minutos, independentemente de quantas oportunidades
   ou clientes o referenciem.

O enriquecimento é sempre **não-fatal**: qualquer falha resulta na ausência do
campo, nunca em erro do request.
