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

# Transmitir PnL de wallets y posiciones Solana

> Recibe PnL de wallets, posiciones y resúmenes. Añade precios en vivo para actualizar el rendimiento no realizado.

Carga un snapshot REST y mantén los totales o posiciones con una sala PnL. Añade una sala de precios para actualizar la valoración entre trades.

<Info>
  PnL V2 Datastream está disponible en los planes **Premium, Business y Enterprise**.\
  Conéctate a `wss://datastream.solanatracker.io/{apiKey}` con tu clave de la Data API.
</Info>

## Tipos de sala

Hay tres salas, cada una para un caso de uso distinto:

| Sala | Qué emite | Cuándo usar |
| - | - | - |
| `pnl:{wallet}:{token}` | `tradeUpdate`, `balanceUpdate`, `priceUpdate` para un token | Sigues una posición específica |
| `pnl:{wallet}` | `tradeUpdate`, `balanceUpdate`, `priceUpdate` para todos los tokens | Quieres actualizaciones en vivo de cada posición de un wallet |
| `pnl:{wallet}:summary` | Resumen total de PnL del wallet | Quieres totales agregados a nivel de wallet |

<Note>
  Las salas de PnL emiten `tradeUpdate` en cada fill y `balanceUpdate` en cambios de saldo. También emiten `priceUpdate`, pero **con menor frecuencia**. Considera `priceUpdate` como un mensaje de actualización para posiciones inactivas, no como un feed rápido de precios. Para los ticks de PnL no realizado más rápidos, combina las salas de PnL con un [stream de precios](#fastest-live-unrealized-pnl-combining-rooms).
</Note>

## Conexión

```javascript theme={null}
const ws = new WebSocket("wss://datastream.solanatracker.io/YOUR_API_KEY");

ws.onopen = () => {
  // subscribe to a room
  ws.send(JSON.stringify({
    type: "join",
    room: "pnl:WALLET_ADDRESS:TOKEN_ADDRESS"
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log(msg.type, msg.data);
};
```

## Sala: pnl:\{wallet}:\{token}

Suscríbete a una posición específica de un token en un wallet. Los mensajes de trade y saldo actualizan la posición; `priceUpdate` refresca periódicamente los valores que dependen del precio.

**`tradeUpdate`** reports a buy or sell:

```json theme={null}
{
  "type": "message",
  "room": "pnl:DJqkH...D61:A7Fbn...pump",
  "data": {
    "type": "tradeUpdate",
    "wallet": "DJqkH...D61",
    "token": "A7Fbn...pump",
    "buyCount": 3,
    "sellCount": 1,
    "totalBuyUsd": 0.0257,
    "totalSellUsd": 0.0043,
    "avgCostPerToken": 0.000082,
    "currentBalance": 205.39,
    "currentPrice": 0.0000911,
    "currentValue": 0.01871,
    "holdingCostBasis": 0.01715,
    "realizedPnl": -0.00087,
    "unrealizedPnl": 0.00156,
    "proceeds": 0.0043,
    "totalTransactions": 4
  }
}
```

**`balanceUpdate`** reports a position balance update:

```json theme={null}
{
  "type": "message",
  "room": "pnl:DJqkH...D61:A7Fbn...pump",
  "data": {
    "type": "balanceUpdate",
    "wallet": "DJqkH...D61",
    "token": "A7Fbn...pump",
    "avgCostPerToken": 0.0000835,
    "currentBalance": 205.39,
    "currentPrice": 0.0000911,
    "currentValue": 0.01871,
    "holdingCostBasis": 0.01715,
    "unrealizedPnl": 0.00156
  }
}
```

### Ejemplo de suscripción

```javascript theme={null}
ws.send(JSON.stringify({
  type: "join",
  room: "pnl:DJqkHSmx9XosFpNqdi2DevtNgaf52oow4pFpJECv6D61:A7FbnhhkY2R6hZhkT2oexNNFQyGG7cBZJLWmC32spump"
}));
```

## Sala: pnl:\{wallet}

Suscríbete a **todas** las posiciones de tokens de un wallet a la vez. Emite los mismos mensajes `tradeUpdate`, `balanceUpdate` y `priceUpdate`, pero para cada token con el que interactúa el wallet — no necesitas conocer las direcciones de los tokens de antemano.

```javascript theme={null}
ws.send(JSON.stringify({
  type: "join",
  room: "pnl:DJqkHSmx9XosFpNqdi2DevtNgaf52oow4pFpJECv6D61"
}));

ws.onmessage = (event) => {
  const { data } = JSON.parse(event.data);
  if (!data) return;

  if (data.type === "tradeUpdate") {
    console.log(`Trade on ${data.token}: unrealized PnL $${data.unrealizedPnl.toFixed(4)}`);
  }
  if (data.type === "balanceUpdate") {
    console.log(`Price update for ${data.token}: value now $${data.currentValue.toFixed(4)}`);
  }
};
```

<Tip>
  Usa `pnl:{wallet}` cuando crees un dashboard de portfolio en vivo — solo necesitas una suscripción por wallet.
</Tip>

## Sala: pnl:\{wallet}:summary

Recibe el resumen total de PnL del wallet cada vez que cambia el agregado. Esta sala devuelve totales a nivel de wallet, no posiciones individuales.

```json theme={null}
{
  "type": "message",
  "room": "pnl:DJqkH...D61:summary",
  "data": {
    "averages": {
      "buy": 1.1751933473135445,
      "sell": 1.7625162156883138
    },
    "counts": {
      "buys": 1664,
      "sells": 745,
      "tokensHeldEver": 842,
      "tokensTraded": 862,
      "trades": 2409
    },
    "invested": 708.3246017180338,
    "openPositions": {
      "cost": 962.9218710198314,
      "value": 37.196319618271204
    },
    "pnl": {
      "realized": 604.7499789697601,
      "total": -421.18843262884593,
      "unrealized": -1025.938411598606
    },
    "proceeds": 1313.0745806877937,
    "roi": -59.46262936614906,
    "timing": {
      "firstTrade": 1704067810000,
      "lastTrade": 1778966213113
    }
  }
}
```

```javascript theme={null}
ws.send(JSON.stringify({
  type: "join",
  room: "pnl:DJqkHSmx9XosFpNqdi2DevtNgaf52oow4pFpJECv6D61:summary"
}));

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.room !== `pnl:${wallet}:summary`) return;
  const { data } = msg;

  console.log(`Total PnL: $${data.pnl.total.toFixed(2)}`);
  console.log(`Realized: $${data.pnl.realized.toFixed(2)}`);
  console.log(`Unrealized: $${data.pnl.unrealized.toFixed(2)}`);
  console.log(`ROI: ${data.roi.toFixed(2)}%`);
};
```

<a id="fastest-live-unrealized-pnl-combining-rooms" />

## Actualiza el PnL no realizado con precios en vivo

Las salas de PnL te dan el cost basis oficial, PnL realizado en FIFO y cambios de saldo cuando llega una operación. FIFO significa "first in, first out": las compras más antiguas se emparejan primero con las ventas. Las salas de PnL no están diseñadas como un feed de precios tick a tick. Los eventos `priceUpdate` están throttled y solo se emiten para posiciones inactivas durante un tiempo.

Para una UI de portfolio en tiempo real donde el número de PnL no realizado se actualice en cada tick de precio, **combina la sala de PnL con un stream de precios**:

| Stream | Qué te da |
| - | - |
| `pnl:{wallet}` (o `pnl:{wallet}:{token}`) | Cost basis en vivo, PnL realizado FIFO, cambios de saldo, fills |
| `price:aggregated:{token}` | Resumen de precios entre pools |
| `price-by-token:{token}` | Precio del pool principal |

Usa la sala de PnL como fuente de verdad para `currentBalance` y `holdingCostBasis`, y recalcula `unrealizedPnl = (currentBalance × livePrice) − holdingCostBasis` cada vez que llega un tick de precio.

### Ejemplo: PnL no realizado en vivo sobre una sola posición

```javascript theme={null}
const wallet = "DJqkHSmx9XosFpNqdi2DevtNgaf52oow4pFpJECv6D61";
const token  = "A7FbnhhkY2R6hZhkT2oexNNFQyGG7cBZJLWmC32spump";

const ws = new WebSocket("wss://datastream.solanatracker.io/YOUR_API_KEY");

const position = {
  balance: 0,
  costBasis: 0,
  realizedPnl: 0,
  livePrice: 0,
};

const recompute = () => {
  const value = position.balance * position.livePrice;
  const unrealized = value - position.costBasis;
  console.log(
    `value=$${value.toFixed(4)}  unrealized=$${unrealized.toFixed(4)}  realized=$${position.realizedPnl.toFixed(4)}`
  );
};

ws.onopen = () => {
  ws.send(JSON.stringify({ type: "join", room: `pnl:${wallet}:${token}` }));
  ws.send(JSON.stringify({ type: "join", room: `price:aggregated:${token}` }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type !== "message") return;
  const d = msg.data;

  if (d?.type === "tradeUpdate" || d?.type === "balanceUpdate") {
    position.balance   = d.currentBalance;
    position.costBasis = d.holdingCostBasis;
    if (d.type === "tradeUpdate") position.realizedPnl = d.realizedPnl;
    position.livePrice = d.currentPrice;
    recompute();
    return;
  }

  if (msg.room?.startsWith("price:aggregated:")) {
    position.livePrice = d.price;
    recompute();
  }
};
```

### Ejemplo: PnL no realizado en vivo en todo un wallet

Suscríbete a `pnl:{wallet}` para recibir trade y balance updates en cada posición, y luego suscríbete dinámicamente a una sala de precio para cada token del wallet.

<Warning>
  Los streams de precio pueden actualizarse a menudo. En producción, agrupa las actualizaciones de UI o aplica throttle a los logs para que cada tick no inunde tu consola o navegador.
</Warning>

```javascript theme={null}
const wallet = "DJqkHSmx9XosFpNqdi2DevtNgaf52oow4pFpJECv6D61";
const ws = new WebSocket("wss://datastream.solanatracker.io/YOUR_API_KEY");
const positions = new Map(); // token -> { balance, costBasis, realizedPnl, livePrice }
const subscribed = new Set();

const portfolioUnrealized = () => {
  let total = 0;
  for (const p of positions.values()) {
    total += p.balance * p.livePrice - p.costBasis;
  }
  return total;
};

const subscribePrice = (token) => {
  if (subscribed.has(token)) return;
  subscribed.add(token);
  // Use price:aggregated for high-liquidity tokens, price-by-token for memecoins / single-pool
  ws.send(JSON.stringify({ type: "join", room: `price:aggregated:${token}` }));
};

ws.onopen = () => {
  ws.send(JSON.stringify({ type: "join", room: `pnl:${wallet}` }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type !== "message") return;
  const d = msg.data;

  if (d?.type === "tradeUpdate" || d?.type === "balanceUpdate") {
    const p = positions.get(d.token) ?? { balance: 0, costBasis: 0, realizedPnl: 0, livePrice: 0 };
    p.balance   = d.currentBalance;
    p.costBasis = d.holdingCostBasis;
    p.livePrice = d.currentPrice;
    if (d.type === "tradeUpdate") p.realizedPnl = d.realizedPnl;
    positions.set(d.token, p);
    subscribePrice(d.token);
  }

  if (msg.room?.startsWith("price:aggregated:")) {
    const token = msg.room.split(":")[2];
    const p = positions.get(token);
    if (!p) return;
    p.livePrice = d.price;
    positions.set(token, p);
  }

  console.log(`Portfolio unrealized PnL: $${portfolioUnrealized().toFixed(4)}`);
};
```

<Tip>
  **¿Qué sala de precio?**

  * `price:aggregated:{token}` — Precio mediano entre pools.
  * `price-by-token:{token}` — Precio del pool principal.
</Tip>

### Payload de `priceUpdate` (sala PnL)

Las salas de PnL emiten `priceUpdate` como respaldo cuando la cotización de una posición no se ha movido durante un tiempo. Tómalo como una actualización periódica, no como un feed en tiempo real:

```json theme={null}
{
  "type": "message",
  "room": "pnl:DJqkH...D61:A7Fbn...pump",
  "data": {
    "type": "priceUpdate",
    "wallet": "DJqkH...D61",
    "token": "A7Fbn...pump",
    "currentBalance": 205.39,
    "currentPrice": 0.0000911,
    "currentValue": 0.01871,
    "holdingCostBasis": 0.01715,
    "unrealizedPnl": 0.00156
  }
}
```

## Cancelar suscripción

Sal de cualquier sala enviando un mensaje `leave`:

```javascript theme={null}
ws.send(JSON.stringify({
  type: "leave",
  room: "pnl:DJqkHSmx9XosFpNqdi2DevtNgaf52oow4pFpJECv6D61:summary"
}));
```

## Guías relacionadas

<CardGroup cols={2}>
  <Card title="Guías de PnL" href="/es/guides/pnl-v2/index" icon="https://mintcdn.com/solanatracker/l0aXlX5jJ7xpUiwY/icons/hugeicons/chart-bar.svg?fit=max&auto=format&n=l0aXlX5jJ7xpUiwY&q=85&s=973c891d0d519ce60de75e347ba52334" iconType="duotone" width="24" height="24" data-path="icons/hugeicons/chart-bar.svg">
    Conceptos centrales de PnL V2, endpoints y cómo funciona la indexación de wallets.
  </Card>

  <Card title="Precios en vivo" href="/es/guides/datastream-prices" icon="https://mintcdn.com/solanatracker/l0aXlX5jJ7xpUiwY/icons/hugeicons/bolt.svg?fit=max&auto=format&n=l0aXlX5jJ7xpUiwY&q=85&s=51002790abf5a801d7cbd83a4f27a96e" iconType="duotone" width="24" height="24" data-path="icons/hugeicons/bolt.svg">
    Salas `price:aggregated` y `price-by-token` — combínalas con PnL para PnL no realizado en vivo.
  </Card>

  <Card title="PnL de la wallet" href="/es/guides/pnl-v2/wallet-analysis" icon="https://mintcdn.com/solanatracker/4PIKDaYde_747jyk/icons/hugeicons/wallet.svg?fit=max&auto=format&n=4PIKDaYde_747jyk&q=85&s=4508ce92a8d0cb33c550a1e3ac7c5bef" iconType="duotone" width="24" height="24" data-path="icons/hugeicons/wallet.svg">
    Analiza el PnL del wallet, posiciones, timing de trades y exposición con la REST API.
  </Card>

  <Card title="Posiciones y trades" href="/es/guides/wallet-tracking" icon="https://mintcdn.com/solanatracker/l0aXlX5jJ7xpUiwY/icons/hugeicons/coins.svg?fit=max&auto=format&n=l0aXlX5jJ7xpUiwY&q=85&s=8c7bfc31d0e6407794d2e1fc9a0b9449" iconType="duotone" width="24" height="24" data-path="icons/hugeicons/coins.svg">
    Combina actividad de wallets en vivo con streams de PnL para un dashboard de portfolio en tiempo real.
  </Card>
</CardGroup>


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