# Portfolio stream

> Keep complete position rows and portfolio summary up to date.

Open `GET /trade/v1/portfolio/stream` with an `Authorization: Bearer …` header. Personal access tokens need `portfolio:read`. The response uses `text/event-stream` and covers your complete AGARA portfolio.

This endpoint allows **300 connection attempts per IP per 60 seconds**, including
reconnects. See [stream connection limits](/docs/rate-limits#stream-connection-attempts).

You can use the existing positions and summary REST endpoints for initial display. Once the stream's synchronization arrives, replace that initial data and apply subsequent stream updates. Ignore REST responses that arrive after synchronization.

## Synchronization [#synchronization]

The first message on every connection has `type: "portfolio.sync"`, a `stream_id`, a decimal-string `seq`, and two objects:

* `positions`: an object containing `positions`, `unavailable_exchanges`, and `as_of`. Each position contains its complete numerical fields, a `market` object, and an `event` object or `null`.
* `summary`: the complete summary response, containing `summaries`.

Replace your local portfolio with this snapshot. Position identity is the pair `(exchange, token_id)`.

## Updates [#updates]

Messages with `type: "portfolio.update"` contain `stream_id`, `seq`, `changes`, and `as_of`. Apply all changes in a message together:

| Change type       | Payload                                                                    | Action                                                           |
| ----------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `position.upsert` | `position` containing every position field, `market`, and nullable `event` | Replace the matching row and its metadata, or prepend a new row. |
| `position.remove` | `exchange` and `token_id`                                                  | Remove the matching row.                                         |
| `summary.replace` | `summary` containing `summaries`                                           | Replace your summary.                                            |

Read each row's title, outcome, logo, event link, and lifecycle state from its `market` object. Its optional `event` object provides event display information. Stream packets do not include separate `markets` or `events` maps; the REST endpoints still do. Removal and summary changes do not replace the metadata of other positions. Monetary amounts remain decimal integer strings in micro units, as in REST. Keep nullable accounting fields nullable.

Current price uses the best bid, then the best ask, then the average acquisition price. If none is available, current price and current value are null. P\&L is null whenever the price or acquisition cost is unavailable. Summary valuation totals are also null when a position has no current value.

For proposed or resolved outcomes, use the returned valuation: each outcome is priced at what one share pays. With a single winner, the winning outcome is priced at 100¢ and the losing outcome at 0¢. On a [split](/docs/automatic-redemption#split-resolutions), each outcome carries its own price, such as 37¢ and 63¢, and `to_win_micro` is the shares times that price, rounded down. Zero is a valid price. Do not overwrite these values with order-book quotes or infer the result yourself.

Held shares and available shares update promptly. Acquisition cost and P\&L can update later. An `AVAILABLE` accounting status means a cost estimate is available; it does not promise that every recent trade is already reflected in that estimate.

Use the portfolio row's lifecycle fields to render portfolio actions. A `market_status` notification can arrive earlier; wait for the updated portfolio row before changing those fields. The portfolio stream and REST endpoints use the same lifecycle state.

## Ordering and reconnecting [#ordering-and-reconnecting]

Parse `seq` as an integer with sufficient precision, such as JavaScript `BigInt`. After accepting a synchronization snapshot:

* Ignore messages with a sequence less than or equal to the last applied sequence.
* Apply an update only when its sequence is exactly one greater and its `stream_id` matches.
* Reconnect if a sequence is missing or the stream identifier changes unexpectedly. Wait for the new synchronization before applying updates.

Reconnect when the response ends. Connections also end periodically so credentials can be checked again. Use retry backoff; REST remains available while reconnecting. Historical replay through `Last-Event-ID` is not supported.

Use a fetch-based SSE reader when you need to set the authorization header. Native browser `EventSource` does not accept custom authorization headers. Heartbeat comments can be ignored.

The web app shows the first synchronization immediately, then updates the display once per second. It processes every message in order between display updates.

Open orders continue to use the [account stream](/docs/account-stream).
