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

# Stream de Preços do Looker

> SSE que emite preços em tempo real de cada cripto monitorada pelo Looker, alimentado pelo cache WebSocket

## 📖 Descrição

Endpoint de **Server-Sent Events (SSE)** que transmite os preços atuais (ask/bid) de **cada criptomoeda configurada no Looker**, uma vez por segundo. Os preços são lidos diretamente do `RealtimePriceCache`, que é alimentado em tempo real pelos handlers WebSocket das exchanges.

<Callout title="Pré-requisito: WebSocket ativo" icon="plug" color="blue">
  Os preços dependem do fluxo WebSocket estar habilitado (`ENABLED_WEBSOCKET=1`). Exchanges não cobertas por WebSocket retornarão eventos com `stale: true`.
</Callout>

***

## 🛠️ Requisição

### Método

`GET`

### URL

```plaintext theme={null}
/v1/lookers/{id}/stream
```

### Parâmetros de Caminho

| Parâmetro | Tipo   | Obrigatório | Descrição                       |
| --------- | ------ | ----------- | ------------------------------- |
| `id`      | string | Sim         | ID do looker (ObjectID MongoDB) |

### Headers Necessários

| Header          | Valor               |
| --------------- | ------------------- |
| `Accept`        | `text/event-stream` |
| `Cache-Control` | `no-cache`          |

### Exemplo de Requisição (curl)

```bash theme={null}
curl --location --no-buffer \
  -H "Accept: text/event-stream" \
  -H "Cache-Control: no-cache" \
  'localhost:8080/v1/lookers/507f1f77bcf86cd799439011/stream'
```

### Exemplo de Requisição (JavaScript)

```javascript theme={null}
const lookerId = '507f1f77bcf86cd799439011';
const source = new EventSource(`/v1/lookers/${lookerId}/stream`);

source.onmessage = (event) => {
    const price = JSON.parse(event.data);
    console.log(`${price.ticker} @ ${price.exchange}: ask=${price.ask} bid=${price.bid} stale=${price.stale}`);
};

source.onerror = () => {
    // EventSource reconecta automaticamente
};

// Ao desmontar:
// source.close();
```

***

## 📤 Resposta

### Headers da Resposta

| Header              | Valor               |
| ------------------- | ------------------- |
| `Content-Type`      | `text/event-stream` |
| `Cache-Control`     | `no-cache`          |
| `Connection`        | `keep-alive`        |
| `X-Accel-Buffering` | `no`                |

### Frequência de Emissão

A cada **1 segundo**, são emitidos **N eventos** — um por cripto configurada no Looker (máximo 6). Todos os eventos do mesmo ciclo são seguidos de um `Flush()` para entrega atômica.

### Formato dos Eventos SSE

```
data: {"ticker":"BTC-USDT","exchange":"binance","market":"SPOT","ask":85420.5,"bid":85418.2,"volume":12345.67,"stale":false,"updatedAt":"2026-04-02T14:00:01Z"}

data: {"ticker":"ETH-USDT","exchange":"binance","market":"SPOT","ask":1832.10,"bid":1831.90,"volume":54321.0,"stale":false,"updatedAt":"2026-04-02T14:00:01Z"}
```

### Estrutura do Payload JSON

```json theme={null}
{
  "ticker": "BTC-USDT",
  "exchange": "binance",
  "market": "SPOT",
  "ask": 85420.50,
  "bid": 85418.20,
  "volume": 12345.67,
  "stale": false,
  "updatedAt": "2026-04-02T14:00:01.000Z"
}
```

### Campos do Payload

| Campo       | Tipo    | Descrição                                                            |
| ----------- | ------- | -------------------------------------------------------------------- |
| `ticker`    | string  | Par de trading em formato `BASE-QUOTE` (ex: `BTC-USDT`)              |
| `exchange`  | string  | Nome da exchange onde o Looker monitora essa cripto                  |
| `market`    | string  | Tipo de mercado (`SPOT` ou `FUTURES`)                                |
| `ask`       | float   | Melhor preço de venda (ask) atual                                    |
| `bid`       | float   | Melhor preço de compra (bid) atual                                   |
| `volume`    | float   | Volume de 24h (pode ter até 60s de defasagem)                        |
| `stale`     | boolean | `true` quando o dado tem mais de 10s ou a exchange não tem WebSocket |
| `updatedAt` | string  | Timestamp ISO 8601 do último tick recebido                           |

***

## 📝 Códigos de Resposta

<Callout type="success">
  **200 OK** + `Content-Type: text/event-stream`: Stream aberto com sucesso. A conexão permanece aberta até o cliente fechar.
</Callout>

<Callout type="warning">
  **400 Bad Request**: `id parameter is required` — Parâmetro `id` não informado na URL.
</Callout>

<Callout type="error">
  **404 Not Found**: `looker not found` — Looker com o ID fornecido não existe no banco.
</Callout>

<Callout type="error">
  **500 Internal Server Error**: `SSE not supported by this server` ou `Failed to load looker`.
</Callout>

***

## 💡 Integração no Frontend (hook React)

O projeto já fornece o hook `useLookerStream` em `src/hooks/useLookerStream.js`:

```jsx theme={null}
import { useLookerStream } from 'src/hooks/useLookerStream';

function LookerDetailsPage({ lookerId }) {
    const { prices, connected } = useLookerStream(lookerId);

    return (
        <div>
            {connected ? '🟢 Conectado' : '🔴 Reconectando...'}
            {cryptos.map(crypto => {
                const key = `${crypto.ticker}:${crypto.exchange}`;
                const price = prices[key];
                return (
                    <CryptoCard
                        key={key}
                        crypto={crypto}
                        priceData={price}
                    />
                );
            })}
        </div>
    );
}
```

### Retorno do Hook

| Propriedade | Tipo    | Descrição                                        |
| ----------- | ------- | ------------------------------------------------ |
| `prices`    | object  | Mapa `"ticker:exchange"` → payload do evento SSE |
| `connected` | boolean | `true` enquanto o `EventSource` estiver aberto   |

***

## ⚠️ Considerações

<Callout title="Campo stale" icon="clock" color="yellow">
  Quando `stale: true`, exiba um indicador visual (ícone de aviso, cor diferente) ao usuário para indicar que o dado pode estar desatualizado. O campo é `true` quando o último tick recebido pelo WebSocket foi há mais de **10 segundos**.
</Callout>

<Callout title="Exchanges sem WebSocket" icon="info-circle" color="blue">
  Exchanges como Mercado Bitcoin e Novadax não possuem WebSocket público. Para cryptos monitoradas nessas exchanges, todos os eventos terão `stale: true`. Uma melhoria futura pode adicionar fallback REST com polling de 5s para essas exchanges.
</Callout>

<Callout title="Lifecycle da conexão" icon="plug" color="green">
  A conexão SSE é mantida até o cliente desconectar (navegação, fechamento de aba). O servidor detecta a desconexão via `r.Context().Done()` e encerra a goroutine imediatamente, sem leak de recursos.
</Callout>
