> ## 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.

# Duração das oportunidades e backtest

> Quanto tempo um spread fica aberto em uma rota e o que teria acontecido operando nele

Um spread de 5% que dura 3 segundos não serve para quem precisa de 20 minutos
para transferir na rede. A análise histórica responde duas perguntas por rota
(par + exchange de compra + exchange de venda):

* **Quanto tempo as oportunidades duram?** Número de janelas acima do
  threshold, duração média, mediana (p50), p90 e máxima, distribuição de
  duração e horário do dia em que as janelas começam.
* **Teria dado para operar?** Um backtest simples com capital, tempo de
  transferência e custos líquidos, que separa as operações completadas das
  que **expiraram** antes de a transferência chegar.

No aplicativo a tela fica em `/analise-historica` (plano premium) e pode ser
aberta pelo botão "Ver análise histórica e backtest" no detalhe de uma
operação.

## Fonte dos dados

Os cálculos usam a coleção MongoDB `operations_audit`, gravada pelo
processamento REST a cada \~19 segundos. Ela tem limitações que valem para
todos os números:

| Limitação | Efeito |
| - | - |
| Só grava spreads brutos entre 4% e 50% | O threshold mínimo é **4%**. Abaixo disso não há dado, então a janela "fecha" quando o spread cai abaixo de 4%. |
| Rotas executáveis só são gravadas com spread **líquido** ≥ 4% (desde o modelo de custo da issue #113) | Para essas rotas o piso real é o líquido, não o bruto. |
| Só o melhor par (menor ask × maior bid) de cada ativo por ciclo | Se outra rota do mesmo ativo ficar melhor, a sua some do histórico naquele ciclo. |
| Cadência de \~19 s | Janelas com uma amostra só aparecem como `< 19s`. A duração tem resolução de um ciclo. |
| Cada ciclo grava a rota duas vezes (cotação nativa e USD) | Linhas com menos de 5 s de diferença são unidas, mantendo o maior spread. |

## Como uma janela é detectada

As amostras da rota são lidas em ordem cronológica. Uma janela começa na
primeira amostra com spread ≥ threshold e termina quando:

* uma amostra fica abaixo do threshold; ou
* a próxima amostra chega mais de **60 s** depois da anterior (cerca de três
  ciclos perdidos, o que indica que o spread saiu da faixa gravada).

A duração é `fim − início`. Com `basis=net` o valor comparado é o
`netSpread` gravado na linha (notional de referência de US\$ 1.000). Em
linhas antigas, sem o modelo de custo, o líquido é estimado com as mesmas
taxas padrão do cálculo de spread líquido (`NET_SPREAD_DEFAULT_TAKER_RATE`,
`NET_SPREAD_DEFAULT_WITHDRAWAL_USD`).

A incidência por hora é contada pelo início de cada janela, em UTC, e a tela
converte para o fuso do navegador.

## Sugestão de tempo de transferência

A resposta traz `suggestedTransfer`, calculado assim: a rede mais usada nas
janelas × a mediana de confirmações gravada em `fees.network.confirmations`
× um tempo de bloco aproximado por rede, mais 5 minutos de processamento da
exchange. Redes sem tempo de bloco conhecido não recebem sugestão. É só um
valor inicial: o usuário pode alterar o campo.

## Hipóteses do backtest

* **Capital único e uma operação por janela.** Entra assim que o spread
  atinge o threshold e o capital está livre. Janelas que começam e terminam
  com o capital em trânsito contam como `skippedBusy`. O rebalanceamento de
  volta para a exchange de compra não é modelado.
* **Rota inviável.** Amostras com `executable=false` (sem rede comum ou com
  saque/depósito suspenso) não abrem operação. Uma janela só com amostras
  assim conta como `skippedInfeasible`.
* **Completada.** A janela continua aberta quando a transferência chega
  (`entrada + transferMinutes`). O resultado usa o spread bruto da primeira
  amostra a partir da chegada, e não o da entrada. É a hipótese
  conservadora.
* **Expirada.** A janela fechou antes da chegada. Como não existe dado abaixo
  de 4%, a hipótese é spread 0 com pagamento dos custos.
* **Custos.** `líquido% = bruto − (takerCompra + takerVenda) × 100 − saqueUSD / capital × 100`,
  com as taxas da amostra de entrada. O custo de saque é fixo em USD, por
  isso pesa menos com capital maior. Operações cuja amostra não tinha
  custos completos são marcadas com `costsEstimated` e contadas em
  `estimatedCosts`.
* **Em andamento.** Uma operação aberta cuja janela ainda não fechou no fim do
  histórico entra em `unresolved` e não soma no resultado.

## Endpoints

* [`GET /v1/opportunity-history/stats`](/api-reference/endpoint/get-opportunity-history-stats)
* [`POST /v1/opportunity-history/backtest`](/api-reference/endpoint/post-opportunity-history-backtest)

Os dois estão no limitador de arbitragem (30 req/min por IP), têm timeout de
10 s e cache de 30 minutos por combinação de parâmetros. Na inicialização o
backend cria em `operations_audit` o índice
`{ticker, best_buy_exchange_name, best_sell_exchange_name, timestamp}` usado
por essas consultas.

## Código

* `crypto-core/internal/application/opportunity/`: detecção das janelas, estatísticas, backtest e sugestão de transferência
* `crypto-core/internal/infra/db/opportunity_history.go`: leitura em streaming com projeção e o índice
* `crypto-core/internal/api/opportunity-history-api.go`: handlers HTTP
* `front-argitragem/src/pages/opportunity-history/`: a tela


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