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

# Streams & Account

> Read your options account, and follow quotes, Greeks, positions, and open orders with the Go SDK.

This page builds a program that reads your account over HTTP, then follows the nearest expiry of a market over the WebSocket: its quotes, mark prices and Greeks, and your positions and open orders. It picks up from the [Quickstart](/latest/options/go-sdk/quickstart).

## Read the Account

The account reads are methods on the client:

```go theme={null}
summary, err := client.AccountSummary(ctx)
if err != nil {
	return err
}
fmt.Printf("equity %s USDT, available %s\n",
	summary.TotalEquity, summary.AvailableBalance)

positions, err := client.Positions(ctx)
if err != nil {
	return err
}
for _, p := range positions {
	fmt.Printf("position %s: %s, unrealized PnL %s\n",
		p.Contract, p.Qty, p.UnrealizedPnL)
}
```

The fields are those of [Get Account Summary](/latest/options/rest-api/get-account-summary) and [Get Positions](/latest/options/rest-api/get-account-positions), and monetary amounts are USDT. Amounts are strings, as on the wire, so no precision is lost.

`OpenOrders`, `RecentOrders`, `Deposits` and `Withdrawals` return one `options.Page` at a time. Pass `Next` back as the `PageToken` until it comes back empty. A page can hold fewer items than asked for and still not be the last.

```go theme={null}
deposits := 0
for query := (options.PageQuery{}); ; {
	page, err := client.Deposits(ctx, query)
	if err != nil {
		return err
	}
	deposits += len(page.Items)
	if page.Next == "" {
		break
	}
	query.PageToken = page.Next
}
```

## Subscribe to Streams

A connection follows the contracts of its `options.Scope`, the SDK's [contract filter](/latest/options/websocket-api/overview#contract-filter): a market, narrowed in turn to an expiration, a strike and a type, or every market with `options.ScopeAll()`. The program follows the nearest expiry, found as in the [Quickstart](/latest/options/go-sdk/quickstart). `options.WithSubscriptions` adds streams beyond the [Order Management Stream](/latest/options/websocket-api/order-management-stream) for the connection's scope:

```go theme={null}
expirations, err := client.Expirations(ctx, "BTC-USDT")
if err != nil {
	return err
}
if len(expirations) == 0 {
	return errors.New("no expirations")
}
expiryDate := expirations[0].ExpiryDate
contracts, err := client.Contracts(ctx, "BTC-USDT", expiryDate)
if err != nil {
	return err
}
if len(contracts) == 0 {
	return errors.New("no contracts")
}
// Contract names spell the expiry second: BTC-7FEB26-100000-C.
expiration := strings.Split(contracts[0].ContractName, "-")[1]
scope := options.Scope{Market: "BTC-USDT", Expiration: expiration}
conn, err := client.Connect(ctx, scope, options.WithSubscriptions(
	options.SubscriptionQuotes,
	options.SubscriptionContracts,
	options.SubscriptionPositions,
	options.SubscriptionOpenOrders,
))
if err != nil {
	return err
}
defer conn.Close()
```

`conn.Subscribe` and `conn.Unsubscribe` change the streams later, for the connection's scope or any other. A broader scope replaces the narrower ones under it. The account summary is account-wide, so any scope subscribes it for every market. Subscriptions survive reconnects. One the exchange refuses to restore, such as one over the connection's limit, arrives as an `options.SubscriptionLost` event and is no longer held.

## Handle Events

Everything the connection receives arrives as a typed event on `conn.Events()`. The channel applies backpressure instead of dropping events, so it must be read: a consumer that stops reading stalls the connection.

```go theme={null}
quotes := map[string]options.Quote{} // the latest quote per contract
for event := range conn.Events() {
	switch e := event.(type) {
	case options.QuoteUpdate:
		quotes[e.Quote.Contract] = e.Quote
		fmt.Printf("quote %s: %s / %s\n",
			e.Quote.Contract, e.Quote.BestBid, e.Quote.BestAsk)
	case options.ContractUpdate:
		fmt.Printf("mark %s: %s, IV %s, delta %s\n",
			e.ContractName, e.MarkPrice, e.MarkIV, e.Delta)
	case options.PositionsDelta:
		for _, p := range e.Positions {
			fmt.Printf("position %s: %s %s\n", p.Contract, p.Side, p.Qty)
		}
	case options.OpenOrdersDelta:
		for _, c := range e.Changes {
			fmt.Printf("open orders %s: %s %s\n", e.Contract, c.Kind, c.Order.OrderID)
		}
	case options.Liquidation:
		fmt.Printf("liquidation on %s: margin ratio %s\n", e.Contract, e.MarginRatio)
	}
}
```

Each stream has its event:

| Stream | Event | Snapshot |
| - | - | - |
| `SubscriptionContracts` | `ContractUpdate` | `conn.EmitContracts` |
| `SubscriptionQuotes` | `QuoteUpdate` | `conn.EmitQuotes` |
| `SubscriptionPublicOrderBook` | `OrderBookDelta` | `conn.EmitOrderBook` |
| `SubscriptionPublicTrades` | `PublicTrades` | `conn.EmitPublicTrades` |
| `SubscriptionPositions` | `PositionsDelta` | `conn.EmitPositions` |
| `SubscriptionOpenOrders` | `OpenOrdersDelta` | `conn.EmitOpenOrders` |
| `SubscriptionRecentOrders` | `RecentOrdersDelta` | `conn.EmitRecentOrders` |
| `SubscriptionSettlements` | — | `conn.EmitSettlements` |
| `SubscriptionAccountSummary` | — | `conn.EmitAccountSummary` |

A position that closes arrives once in a `PositionsDelta`, with `Side` `options.PositionSideFlat`. For the contracts the Order Management Stream covers, orders you placed elsewhere arrive too: a fill as `options.OrderFilled`, a refusal as `options.OrderRejected`, a cancellation as `options.OrderCancelled`.

## Take Snapshots

A snapshot is the state a stream continues from, and it is delivered on its stream. Each `Emit` method therefore needs that stream held for a scope that covers what it names. Without one it returns `options.ErrNotSubscribed`, having sent nothing.

Only the Order Management Stream is replayed after a reconnect, so take a new snapshot of every other stream on each `options.Connected`, which also arrives for the first socket. `EmitQuotes` returns one page of an expiry's quotes at a time.

A snapshot is a request on the connection. It is asked for off the event loop, which has to keep reading events while it waits:

```go theme={null}
snapshots := make(chan []options.Quote, 1)
```

```go theme={null}
switch event.(type) {
case options.Connected:
	go func() {
		query := options.ExpiryQuery{Market: "BTC-USDT", ExpiryDate: expiryDate}
		for {
			page, err := conn.EmitQuotes(ctx, query)
			if err != nil {
				fmt.Fprintln(os.Stderr, "quotes snapshot:", err)
				return
			}
			select {
			case snapshots <- page.Items:
			case <-ctx.Done():
				return
			}
			if page.Next == "" {
				return
			}
			query.PageToken = page.Next
		}
	}()
}
```

The event loop reads the snapshot pages alongside `conn.Events()`. Updates keep arriving while a snapshot is on its way, so a quote from the snapshot replaces the one held only if it is newer:

```go theme={null}
select {
case <-ctx.Done():
	return ctx.Err()
case snapshot := <-snapshots:
	for _, q := range snapshot {
		if held, ok := quotes[q.Contract]; !ok || q.Ts >= held.Ts {
			quotes[q.Contract] = q
		}
	}
}
```

## After a Reconnect

`options.SessionBackfill` reports how the [session](/latest/websocket-session) was recovered, on the first connection and after each reconnect. Contracts the replay could not cover ([limits](/latest/websocket-session#limits)) are listed in `ExpiredMarkets`. When the session could not be resumed, or any contract is listed there, rebuild your order state over HTTP, following `Next` through every page as above; the example reads the first:

```go theme={null}
switch e := event.(type) {
case options.SessionBackfill:
	if !e.Resumed || len(e.ExpiredMarkets) > 0 {
		page, err := client.OpenOrders(ctx, options.PageQuery{})
		if err != nil {
			return err
		}
		fmt.Printf("rebuilt: %d open orders on the first page\n", len(page.Items))
	}
}
```

## Run the Example

The complete program is [`examples/options-streams-and-account`](https://github.com/rails-xyz/exchange-go-sdk/tree/main/examples/options-streams-and-account) in the SDK repository. Run it for as long as you want to watch:

```bash theme={null}
RAILS_API_KEY=<key> RAILS_API_SECRET=<secret> go run ./examples/options-streams-and-account -for 30s
```

```text theme={null}
equity 1983.2678417 USDT, available 1983.2678417
0 deposits
rebuilt: 0 open orders on the first page
34 quotes on 2026-10-09
mark BTC-9OCT26-76000-C: 8304.9140645, IV 0.4067, delta 0.9728381
mark BTC-9OCT26-76000-P: 48.4440645, IV 0.4067, delta -0.0271619
```

The first `rebuilt` line is the fresh session reporting that nothing was resumed.

## Related

* [Positions & Account](/latest/options/guides/positions-and-account) and [Market Data](/latest/options/guides/market-data)
* [WebSocket overview](/latest/options/websocket-api/overview), [Quotes Stream](/latest/options/websocket-api/quotes-stream) and [Contracts Stream](/latest/options/websocket-api/contracts-stream)
* [WebSocket Session](/latest/websocket-session)


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