Skip to main content

📖 Descrição

Endpoint de Server-Sent Events (SSE) que transmite o PNL (Profit and Loss) em tempo real de uma posição de arbitragem spot-futuro aberta na Calculadora. A cada segundo, o servidor busca os preços de mercado atuais (spot + futuro), recalcula o spread e o PNL com base nos valores de entrada registrados, e emite o resultado.
Este endpoint substitui o mecanismo de polling manual do frontend (chamadas POST /v1/calculators/process a cada 5 segundos). Com SSE, a atualização é server-driven e mais eficiente — sem overfetching.

🛠️ Requisição

Método

GET

URL

/v1/calculators/stream

Query Parameters

ParâmetroTipoObrigatórioDescrição
emailstringSimEmail do usuário dono da posição
tickerstringSimPar de trading (ex: BTC-USDT)
exchangestringSimExchange da posição (ex: binance)

Headers Necessários

HeaderValor
Accepttext/event-stream
Cache-Controlno-cache

Exemplo de Requisição (curl)

curl --location --no-buffer \
  -H "Accept: text/event-stream" \
  -H "Cache-Control: no-cache" \
  'localhost:8080/v1/calculators/stream?email=user@example.com&ticker=BTC-USDT&exchange=binance'

Exemplo de Requisição (JavaScript)

const params = new URLSearchParams({
    email: 'user@example.com',
    ticker: 'BTC-USDT',
    exchange: 'binance'
});

const source = new EventSource(`/v1/calculators/stream?${params}`);

source.onmessage = (event) => {
    const pnl = JSON.parse(event.data);
    console.log(`PNL: $${pnl.pnl.toFixed(2)} (${pnl.pnlPercentage.toFixed(2)}%) - Lucrativo: ${pnl.isProfitable}`);
};

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

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

📤 Resposta

Headers da Resposta

HeaderValor
Content-Typetext/event-stream
Cache-Controlno-cache
Connectionkeep-alive
X-Accel-Bufferingno

Frequência de Emissão

Um evento é emitido a cada 1 segundo. Internamente, os dados de mercado (spot + futuro) são buscados do MongoDB com cache de 5 segundos para evitar sobrecarga no banco.

Formato dos Eventos SSE

data: {"email":"user@example.com","ticker":"BTC-USDT","exchange":"binance","entrySpotValue":83000,"entryShortValue":83150,...,"pnl":42.50,"pnlPercentage":0.026,"isProfitable":true}

data: {"email":"user@example.com","ticker":"BTC-USDT","exchange":"binance","entrySpotValue":83000,"entryShortValue":83150,...,"pnl":51.30,"pnlPercentage":0.031,"isProfitable":true}

Estrutura Completa do Payload JSON

{
  "email": "user@example.com",
  "ticker": "BTC-USDT",
  "exchange": "binance",
  "entrySpotValue": 83000.00,
  "entryShortValue": 83150.00,
  "entrySpotQty": 0.1,
  "entryFuturesQty": 0.1,
  "entrySpread": 150.00,
  "entrySpreadPercentage": 0.18,
  "currentSpotPrice": 85400.00,
  "currentFuturePrice": 85450.00,
  "currentSpread": 50.00,
  "currentSpreadPercentage": 0.058,
  "fundingRate": 0.0001,
  "pnl": 42.50,
  "pnlPercentage": 0.026,
  "isProfitable": true
}

Campos do Payload

CampoTipoDescrição
emailstringEmail do usuário
tickerstringPar de trading
exchangestringExchange da posição
entrySpotValuefloatPreço spot de entrada (registrado na posição)
entryShortValuefloatPreço futuro de entrada (registrado na posição)
entrySpotQtyfloatQuantidade spot de entrada
entryFuturesQtyfloatQuantidade futuros de entrada
entrySpreadfloatSpread no momento da abertura (futuro − spot)
entrySpreadPercentagefloatSpread de entrada em %
currentSpotPricefloatPreço spot atual
currentFuturePricefloatPreço futuro atual
currentSpreadfloatSpread atual (futuro − spot)
currentSpreadPercentagefloatSpread atual em %
fundingRatefloatTaxa de funding atual
pnlfloatLucro/prejuízo total em USDT
pnlPercentagefloatLucro/prejuízo em % sobre o capital empregado
isProfitablebooleantrue se pnl > 0

Como o PNL é Calculado

spotValueChange    = (currentSpotPrice − entrySpotValue) × entrySpotQty
futureValueChange  = (entryShortValue − currentFuturePrice) × entryFuturesQty
PNL                = spotValueChange + futureValueChange

entryTotalValue    = (entrySpotValue × entrySpotQty) + (entryShortValue × entryFuturesQty)
PNLPercentage      = (PNL / entryTotalValue) × 100

📝 Códigos de Resposta

200 OK + Content-Type: text/event-stream: Stream aberto com sucesso.
400 Bad Request: Parâmetros obrigatórios ausentes:
  • email is required
  • ticker is required
  • exchange is required
404 Not Found: calculator not found — Não há posição salva para o par email + ticker fornecido. Salve os dados de entrada via POST /v1/calculators primeiro.

💡 Integração no Frontend (hook React)

O projeto já fornece o hook useCalculatorStream em src/hooks/useCalculatorStream.js:
import { useCalculatorStream } from 'src/hooks/useCalculatorStream';

function CalculatorPage({ ticker }) {
    const { user } = useAuth();
    const [entryData, setEntryData] = useState(null);
    const [exitOpen, setExitOpen] = useState(true);

    // Stream só é aberto quando a posição está salva e "exitOpen" está ativo
    const streamActive = exitOpen && !!entryData && !!user?.email;
    const { result: processData, connected } = useCalculatorStream(
        user?.email,
        ticker,
        'binance',
        streamActive
    );

    return (
        <div>
            <span>{connected ? '🟢 Live' : '⚪ Aguardando'}</span>
            {processData && (
                <div style={{ color: processData.isProfitable ? 'green' : 'red' }}>
                    PNL: ${processData.pnl?.toFixed(2)} ({processData.pnlPercentage?.toFixed(2)}%)
                </div>
            )}
        </div>
    );
}

Parâmetros do Hook

ParâmetroTipoDescrição
emailstringEmail do usuário
tickerstringPar de trading
exchangestringExchange selecionada
activebooleanLiga/desliga o stream. false fecha o EventSource imediatamente

Retorno do Hook

PropriedadeTipoDescrição
resultobject | nullÚltimo payload recebido do servidor
connectedbooleantrue enquanto o EventSource estiver aberto

⚠️ Considerações

O stream retorna 404 se não houver uma entrada no banco para email + ticker. Sempre salve os dados de entrada via POST /v1/calculators antes de abrir o stream.
Os dados de mercado (spot + futuro) são buscados do MongoDB com cache interno de 5 segundos. Isso significa que o PNL pode ter até 5s de defasagem em relação ao mercado real — aceitável para monitoramento de posições de arbitragem, que não requerem latência de milissegundos.
Diferente do stream do Looker (que usa o RealtimePriceCache WebSocket), o stream da Calculadora usa a coleção operations_future do MongoDB para obter o par de preços spot + futuro. Essa coleção é populada continuamente pelo motor de arbitragem.