# Wire format


Source: https://docs.stxapp.io/websockets/channels/wire-format/

The [account channels](/websockets/#account-channels) and the three
[market-data feeds](/websockets/channels/market-data/) write money and contract
counts as **strings**, in the same format as the [REST API](/api/rest/). One parser and
one set of fixtures cover both surfaces.

The `markets` and `market_updates` metadata feeds are the exception: their prices are
JSON numbers in cents, not dollar strings. Their own pages give the units field by
field.

## The format

**Money is a string of dollars**, with at least four decimal places:

```json
{ "price": "0.6700", "amount": "39.0000", "max_price": "1.0000" }
```

**Quantities and contract counts are strings**, with at least two decimal places:

```json
{ "quantity": "100.00", "filled": "50.00" }
```

A `null` stays `null`; it never becomes `"0.0000"`.

### Decimal places are a minimum, not a fixed width

Almost every money field is exactly four decimal places. Order `price` is the
exception on the channels: it is stored to seven, so `"0.0125432"` is a valid price.

**Parse money with a decimal type that accepts a variable scale.** A parser
hard-coded to four places breaks on that field and nowhere else. Over REST,
`GET /api/v1/fills` carries one more such field, `unrounded_trade_fee`, at up to
nine places.

### What is not converted

Only amounts of money and numbers of contracts become strings. These stay JSON
numbers:

- percentages: `filled_percentage`, `price_change24h`
- counts of objects: `open_order_count`, `trade_count`, `settlements_count`
- fee factors: `base_fee_percent`, `taker_factor`, `maker_factor`
- loyalty `points`

Timestamps come in two shapes and neither is a money string: `time`, `settled_at`
and `timestamp` are ISO 8601, while `inserted_at`, `accepted_at`, `delayed_until`,
`expiration_time`, `expires_at` and `timestamp_us` are integers of Unix **microseconds**.

:::note[Prices read as probabilities]
A market's prices run from 0 to its `max_price` of `"1.0000"`, so `"0.6700"` reads
directly as a 67% probability.
:::

## `total_fee` is the all-in fee

On both [`fills`](/websockets/channels/fills/) and `GET /api/v1/fills`, `total_fee` is the
trade fee plus the settlement fee: what the trade has actually cost you. `trade_fee`
is the on-trade component alone, and is also sent, so `total_fee - trade_fee` is the
settlement part. The two surfaces agree, so a REST snapshot and a `fills` delta can
be mixed freely.

<a id="filtering"></a>

## Filtering by market
[`orders`](/websockets/channels/orders/), [`fills`](/websockets/channels/fills/),
[`positions`](/websockets/channels/positions/), [`settlements`](/websockets/channels/settlements/) and
[`account`](/websockets/channels/account/) take an optional `market_ids` filter in the join
payload, so a client watching a few markets is not sent every order, fill, position
and settlement on the account.

```
["1","1","orders:<user_id>","phx_join",{"market_ids":["<uuid>","<uuid>"]}]
```

The reply echoes what was actually applied:

```json
{"status":"ok","response":{"selected_market_ids":["<uuid>","<uuid>"]}}
```

It applies to both the snapshot you get on join and every push afterwards.

Omit `market_ids`, or send `null` or `[]`, and nothing is filtered;
`selected_market_ids` comes back `null`. Ids you send that are not valid UUIDs are
dropped rather than rejected, and **if none of them are valid you get no filter at
all**, not an empty one. That is why the applied set is echoed: compare it against
what you sent to catch a typo, rather than silently receiving everything.

Ids are case-insensitive.

### Snapshots are filtered differently from deltas

A **snapshot** the filter empties is still sent: `{"orders": []}` on join tells you
there is nothing in those markets, which is worth knowing.

A **delta** the filter empties is not sent at all, rather than arriving as an empty
list, because `{"positions": []}` would read as "your positions are gone". `updated_positions`
and `new_settlements` are the deltas.

### Changing it without rejoining

```
["1","2","orders:<user_id>","select_market_ids",{"market_ids":["<uuid>"]}]
```

The reply carries the new `selected_market_ids`. Send `null` to clear the filter.

[`balances`](/websockets/channels/balances/) does not take this parameter; it is scoped to
one account, not to markets. It takes an optional `account_id` instead.

The market-data feeds narrow differently again: `orderbook` **requires** `market_ids` and
changes it with `select_market_ids`, while `ticker` and `trades` filter on other
fields and use `select_filters`. See
[Order book, ticker and trades](/websockets/channels/market-data/).
