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

# PnL V2 Datastream

> 通过 WebSocket 订阅 Solana 钱包的实时盈亏(PnL)更新——跟踪已实现 PnL、持仓变化与交易活动，构建实时投资组合监控与告警。

<Info>
  PnL V2 Datastream 适用于 **Premium、Business 和 Enterprise** 方案。\
  使用您的 Data API 密钥连接到 `wss://datastream.solanatracker.io/{apiKey}`。
</Info>

PnL V2 Datastream 直接将实时交易和余额更新推送到您的 WebSocket 客户端。无需轮询 REST API,订阅某个 room 即可在事件发生时立即接收更新。

<Info>
  **通俗解释:** PnL 表示盈亏。已实现 PnL 来自已售出的代币。未实现 PnL 是仍持有代币的账面盈亏。成本基础是钱包为当前持仓所支付的金额。
</Info>

## Room 类型

共有三种 room,每种服务于不同的用例:

| Room                   | 它推送什么                                               | 何时使用               |
| ---------------------- | --------------------------------------------------- | ------------------ |
| `pnl:{wallet}:{token}` | 单个代币的 `tradeUpdate`、`balanceUpdate`、`priceUpdate`   | 您在追踪某个特定持仓         |
| `pnl:{wallet}`         | 钱包所有代币的 `tradeUpdate`、`balanceUpdate`、`priceUpdate` | 您希望钱包持有的每个持仓都有实时更新 |
| `pnl:{wallet}:summary` | 总钱包 PnL 摘要                                          | 您希望获得汇总的钱包级 PnL 总计 |

<Note>
  PnL room 在每次成交时推送 `tradeUpdate`,在余额变化时推送 `balanceUpdate`。它们也会推送 `priceUpdate` 事件,但**频率较低**。可将 `priceUpdate` 视为对沉寂头寸的补发消息,而不是快速价格推送。要获得最快的未实现 PnL 跳动,请将 PnL room 与[价格流](#fastest-live-unrealized-pnl-combining-rooms)配合使用。
</Note>

***

## 连接

```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);
};
```

***

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

订阅钱包中某个特定代币的持仓。您将收到两种消息类型:

**`tradeUpdate`** — 当买入或卖出执行时触发:

```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`** — 当持仓价值因价格变动而变化时触发(没有新交易):

```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
  }
}
```

### 订阅示例

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

***

## Room: pnl:\{wallet}

一次订阅某钱包的**所有**代币持仓。推送与上面相同的 `tradeUpdate` 和 `balanceUpdate` 消息,但涵盖钱包交互过的每个代币——无需事先知道代币地址。

```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>
  构建实时投资组合面板时使用 `pnl:{wallet}` — 每个钱包只需一个 room 订阅。
</Tip>

***

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

每当聚合值发生变化时接收钱包的总 PnL 摘要。此 room 返回钱包级总计,而不是单个持仓。

```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)}%`);
};
```

***

## 最快的实时未实现 PnL — 组合 Room

PnL room 在交易上链时为您提供官方的成本基础、FIFO 已实现 PnL 和余额变化。FIFO 表示 "first in, first out"(先进先出):较早的买入先与卖出匹配。PnL room 并非设计为逐笔价格推送。`priceUpdate` 事件受限速,且仅为长时间无变化的持仓补发。

对于 UI 每个价格跳动都要更新未实现 PnL 数字的实时投资组合界面,请**将 PnL room 与价格流组合**:

| 流                                         | 它提供什么                         |
| ----------------------------------------- | ----------------------------- |
| `pnl:{wallet}` (或 `pnl:{wallet}:{token}`) | 实时成本基础、FIFO 已实现 PnL、余额变化、交易成交 |
| `price:aggregated:{token}`                | 最快的跨池价格 — 适合高流动性代币            |
| `price-by-token:{token}`                  | 主池(流动性最高的池)价格 — 适合单池/低流动性代币   |

将 PnL room 用作 `currentBalance` 和 `holdingCostBasis` 的真实来源,并在每次价格跳动到来时重新计算 `unrealizedPnl = (currentBalance × livePrice) − holdingCostBasis`。

### 示例:单一持仓的实时未实现 PnL

```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();
  }
};
```

### 示例:整个钱包的实时未实现 PnL

订阅 `pnl:{wallet}` 以获取每个持仓的交易和余额更新,然后为钱包持有的每个代币动态订阅价格 room。

<Warning>
  价格流可能更新频繁。生产环境中请批量处理 UI 更新或限流日志,避免每次价格跳动都让控制台或浏览器被淹没。
</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>
  **选哪个价格 room?**

  * `price:aggregated:{token}` — 推荐默认。跨池中位数价格;当代币在多个池中交易时更平滑、更准确。
  * `price-by-token:{token}` — 主池价格。对于 memecoin 或实际仅在单一池中交易的代币,延迟略低。
</Tip>

### `priceUpdate` payload(PnL room)

PnL room 会在持仓报价一段时间未变时推送 `priceUpdate` 作为兜底。请将其视为周期性刷新,而不是实时推送:

```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
  }
}
```

***

## 取消订阅

通过发送 `leave` 消息离开任何 room:

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

***

## 选择合适的 Room

<AccordionGroup>
  <Accordion title="我在追踪一个特定持仓">
    使用 `pnl:{wallet}:{token}`。噪音最少 — 仅在该代币上触发。
  </Accordion>

  <Accordion title="我在构建实时投资组合面板">
    使用 `pnl:{wallet}`。一个订阅涵盖所有持仓。每次更新都能获得代币级细节。
  </Accordion>

  <Accordion title="我想要不带每个代币噪音的投资组合摘要">
    使用 `pnl:{wallet}:summary`。它返回聚合的钱包级 PnL 总计,而不包含每个代币的持仓行。
  </Accordion>

  <Accordion title="我希望获得最快的未实现 PnL 跳动">
    将 `pnl:{wallet}`(或 `pnl:{wallet}:{token}`)与 `price:aggregated:{token}` 或 `price-by-token:{token}` 组合。PnL room 提供成本基础和余额;价格 room 驱动实时跳动。请参阅[组合 Room](#fastest-live-unrealized-pnl-combining-rooms)。
  </Accordion>

  <Accordion title="我可以同时订阅多个 room 吗?">
    可以。发送多条 `join` 消息。每个 room 独立运行。
  </Accordion>
</AccordionGroup>

***

## 相关指南

<CardGroup cols={2}>
  <Card title="PnL V2 概览" href="/cn/guides/pnl-v2/index" icon="chart-bar" iconType="duotone">
    核心 PnL V2 概念、端点以及钱包索引的工作方式。
  </Card>

  <Card title="实时价格与图表" href="/cn/guides/datastream-prices" icon="bolt" iconType="duotone">
    `price:aggregated` 和 `price-by-token` room — 将它们与 PnL 配对以获得实时未实现 PnL。
  </Card>

  <Card title="钱包分析" href="/cn/guides/pnl-v2/wallet-analysis" icon="wallet" iconType="duotone">
    使用 REST API 分析钱包 PnL、持仓、交易时机和敞口。
  </Card>

  <Card title="钱包追踪" href="/cn/guides/wallet-tracking" icon="coins" iconType="duotone">
    将实时钱包活动与 PnL 流结合,构建实时投资组合面板。
  </Card>
</CardGroup>
