Smart Balance — WebSocket Notifications

Subscribe to Smart Balance portfolio execution updates over WebSocket. The same connection also carries full open-orders snapshots.

Authentication: HMAC apiKey signature via URL query string at handshake time. See Authentication.

Channels

Smart Balance clients subscribe to two channels:

Name Requires accountId Description
trade-news false Mandatory — rich portfolio lifecycle events (multiple MessageTypes)
order.change.any true Recommended — full open-orders snapshot, pushed on every order change for the subscribed accountId

The WS endpoint also serves other channels (paradigm-news, block-rfq-news, block.trade, brokerage.trade, algo.change.any) that cover unrelated business domains and are outside the scope of this document.

Subscribe

Request

{
  "id": "sub-<uuid>",
  "op": "subscribe",
  "accountId": 10003443,
  "data": [
    {"channel": "trade-news"},
    {"channel": "order.change.any"}
  ]
}

Parameters

Parameter Type Required Description
id string true Client-generated request ID, echoed back in the response
op string true subscribe / un-subscribe / heartbeat
accountId long conditional Required when subscribing order.change.any; omit when subscribing only trade-news
data array true Array of {channel} objects

Response

{
  "id": "sub-<uuid>",
  "op": "subscribe",
  "code": 0,
  "data": [
    {"channel": "trade-news", "success": true, "errorMsg": null},
    {"channel": "order.change.any", "success": true, "errorMsg": null}
  ]
}

Each channel is acknowledged individually. On failure (for example accountId missing) the per-channel success is false with errorMsg populated.

Unsubscribe

{
  "id": "unsub-<uuid>",
  "op": "un-subscribe",
  "data": [
    {"channel": "trade-news"}
  ]
}

Same shape as subscribe; op set to un-subscribe.

Heartbeat

Request

{"id": "hb-1", "op": "heartbeat"}

Response

{"id": "hb-1", "op": "heartbeat", "code": 0}

Server heartbeat timeout is 3 minutes. Client SHOULD send a heartbeat every 10–30 seconds. Missing three consecutive heartbeats causes the server to terminate the connection.

Push Envelope

Every push frame is JSON. trade-news and order.change.any share the same top-level envelope shape but different data semantics.

{
  "id": "<uuid>",
  "channel": "<channel-name>",
  "accountId": "<accountId>",    // only present for order.change.any
  "data": { /* channel-specific */ }
}

Channel: trade-news

Rich event stream. Each push carries a type discriminator (MessageType) plus an inner data payload.

{
  "id": "<uuid>",
  "channel": "trade-news",
  "data": {
    "type": "<MessageType>",
    "data": { /* MessageType-specific payload */ },
    "timestamp": "<millis>"
  }
}

Message Type Summary

Name Type Description
PORTFOLIO_ACTIVE string Portfolio starts running; full PortfolioWsDTO payload
PORTFOLIO_FILLED string Portfolio fully filled; full PortfolioWsDTO + per-leg fill summary
PORTFOLIO_CANCELLED string Portfolio cancelled; full PortfolioWsDTO
PORTFOLIO_CANCELLING string Cancel request accepted, in flight
LEG_ACTIVE string Leg starts accepting fills
LEG_FILLED string Leg fully filled; LegWsDTO + fill summary
LEG_PENDING_PRICE string Leg dynamic price refreshed (high-frequency, lightweight payload)
TRADE string Fill report

Message — PORTFOLIO_ACTIVE

{
  "id": "1b0688d9-4d6c-4ad4-88ef-5c1f999c6e68",
  "channel": "trade-news",
  "data": {
    "type": "PORTFOLIO_ACTIVE",
    "data": {
      "uid": 13554298,
      "nickname": "",
      "accountId": 10003443,
      "accountName": "rfq-test-01",
      "exchange": "DERIBIT",
      "portfolioId": "144115188955249274",
      "submitTimestamp": "1776668666388",
      "portfolioStatus": 1,
      "balanceTrade": true,
      "icebergTrade": false,
      "parts": 3,
      "eachLimitMs": 15000,
      "totalLimitMs": 0,
      "totalEndAction": 2,
      "totalMs": 45000,
      "fees": [],
      "legs": [
        {
          "legId": "216172783206681210",
          "instrument": "BTC-25DEC26-40000-P",
          "instrumentType": "OPTION",
          "quoteCurrency": "BTC",
          "kind": 7,
          "side": 1,
          "type": 1,
          "placeQuantity": "1.5",
          "quantityIn": "BTC",
          "placePrice": "0.6",
          "placePriceIn": "IV",
          "placePriceLabel": 2,
          "placePriceOffset": "0",
          "placeDynamicMs": 30000,
          "priceSnapshot": [
            {"placePriceIn": "USD", "placePrice": "2144.89", "price": "2144.89", "priceIn": "USD"},
            {"placePriceIn": "BTC", "placePrice": "0.031", "price": "0.031", "priceIn": "BTC"},
            {"placePriceIn": "IV", "placePrice": "0.6", "price": "0.6", "priceIn": "IV"}
          ],
          "legStatus": 1,
          "remainingQuantity": "1.5"
        }
      ]
    },
    "timestamp": "1776668666715"
  }
}

PORTFOLIO_ACTIVE Payload

Top-level fields in data.data.

Name Type Description
uid long User ID
accountId long Account ID
accountName string Account display name
exchange string DERIBIT / OKX / BYBIT / ...
portfolioId string Portfolio ID (stringified long)
submitTimestamp string Submit time in ms
portfolioStatus int 1=ACTIVE, 2=FILLED, 3=CANCELLED, 4=CANCELLING, 6=PENDING
balanceTrade boolean true when portfolioType is BALANCE
icebergTrade boolean true when portfolioType is ICEBERG
parts / eachLimitMs / totalLimitMs / totalEndAction / totalMs Balance strategy parameters
fees array Aggregate fees (empty while still running)
legs array Array of Leg objects (see below)

legs[].Leg sub-fields (push shape).

Name Type Description
legId string Leg ID (stringified long)
kind int 1=SPOT, 3=U_PERPETUAL, 5=C_PERPETUAL, 7=C_OPTION, ... (see Enum Reference)
side int 1=BUY, 2=SELL
type int 1=LIMIT, 2=MARKET
legStatus int 1=ACTIVE/PENDING, 2=FILLED, 3=CANCELLED, 4=CANCELLING
placePriceLabel int 1=MID, 2=MARK, 3=MODEL, 4=BID_ASK
placePriceIn string USD / USDT / BTC / IV / ...
priceSnapshot array [{placePriceIn, placePrice, price, priceIn}, ...]
priority boolean true if this leg is the priority (leading) leg that drives portfolio pacing; others follow by ratio. Default false
hedge boolean true if this leg is an auto-derived hedge leg (non-option leg in a mixed option+linear portfolio). Default false

Message — LEG_ACTIVE

{
  "id": "aac64beb-341a-4955-be02-690c3ebde4d9",
  "channel": "trade-news",
  "data": {
    "type": "LEG_ACTIVE",
    "data": {
      "placePriceLabel": 2,
      "placePriceOffset": "0",
      "accountId": 10003443,
      "accountName": "rfq-test-01",
      "exchange": "DERIBIT",
      "legId": "216172783206681210",
      "portfolioId": "144115188955249274",
      "instrument": "BTC-25DEC26-40000-P",
      "instrumentType": "OPTION",
      "baseCurrency": "BTC",
      "quoteCurrency": "BTC",
      "kind": 7,
      "side": 1,
      "type": 1,
      "legStatus": 1,
      "placeQuantity": "1.5",
      "quantityIn": "BTC",
      "placePrice": "0.6",
      "placePriceIn": "IV",
      "priceSnapshot": [
        {"placePriceIn": "USD", "placePrice": "2144.89", "price": "2144.89", "priceIn": "USD"},
        {"placePriceIn": "BTC", "placePrice": "0.031", "price": "0.031", "priceIn": "BTC"},
        {"placePriceIn": "IV", "placePrice": "0.6", "price": "0.6", "priceIn": "IV"}
      ],
      "priceCurrency": "BTC",
      "remainingQuantity": "1.5"
    },
    "timestamp": "1776668667839"
  }
}

Message — TRADE

{
  "id": "764e4291-cb28-4d67-83c7-d8c860bc66f8",
  "channel": "trade-news",
  "data": {
    "type": "TRADE",
    "data": {
      "accountId": 10003443,
      "accountName": "rfq-test-01",
      "exchange": "DERIBIT",
      "portfolioType": 1,
      "portfolioId": "144115188955249274",
      "legId": "216172783206681210",
      "orderId": "288231502551859205",
      "instrument": "BTC-25DEC26-40000-P",
      "instrumentType": "OPTION",
      "side": 1,
      "orderType": "LIMIT",
      "orderPriceIn": "IV",
      "placeQuantity": "1.5",
      "filledQuantity": "0.5",
      "filledAvgPrice": "0.155",
      "remainingQuantity": "1",
      "quantity": "0.5",
      "price": "0.155",
      "fee": "0.00015",
      "role": "TAKER",
      "indexPrice": "74783.2",
      "markPrice": "0.226986",
      "underlyingPrice": "76229.74",
      "iv": "1.4947",
      "forCcy": "BTC",
      "domCcy": "BTC",
      "markCcy": "BTC",
      "notionalCcy": "BTC",
      "defaultPvCcy": "BTC",
      "mode": 2,
      "type": 1,
      "priceCurrency": "BTC",
      "priceIn": "IV",
      "quantityIn": "BTC"
    },
    "timestamp": "1776668668119"
  }
}

TRADE Payload

Name Type Description
portfolioType int PortfolioMode code (int here, not the string used in order.change.any): 1=COMMON, 2=BALANCE, 4=TWAP, 5=BLOCK_RFQ, 6=ICEBERG
orderType string LIMIT / MARKET
placeQuantity string Full order placed amount
filledQuantity string Accumulated filled on this order
remainingQuantity string Order-level remainder
quantity string This fill's delta amount
price string This fill's price
filledAvgPrice string Weighted average fill price across all fills on this order
fee string Fee for this fill (in fee currency)
role string MAKER / TAKER
indexPrice string Market snapshot at fill time
markPrice string Market snapshot at fill time
underlyingPrice string Option only — underlying mark
iv string Option only — implied volatility
mode int PortfolioMode: 1=COMMON_COMBO, 2=BALANCE_COMBO, ...
type int 1=LIMIT / 2=MARKET

A single market order can emit multiple TRADE events (one per fill slice). quantity is per-fill delta; filledQuantity accumulates.

Message — LEG_FILLED

{
  "id": "908b389e-e31a-492e-b9bf-1bc12cc81528",
  "channel": "trade-news",
  "data": {
    "type": "LEG_FILLED",
    "data": {
      "placePriceLabel": 2,
      "placePriceOffset": "0",
      "accountId": 10003443,
      "accountName": "rfq-test-01",
      "exchange": "DERIBIT",
      "legId": "216172783206681210",
      "portfolioId": "144115188955249274",
      "instrument": "BTC-25DEC26-40000-P",
      "instrumentType": "OPTION",
      "baseCurrency": "BTC",
      "quoteCurrency": "BTC",
      "kind": 7,
      "side": 1,
      "type": 1,
      "legStatus": 1,
      "placeQuantity": "1.5",
      "quantityIn": "BTC",
      "placePrice": "0.6",
      "placePriceIn": "IV",
      "priceSnapshot": [ /* ... */ ],
      "pendingPrice": "0.2265",
      "priceCurrency": "BTC",
      "filledQuantity": "1.5",
      "filledAvgPrice": "0.155",
      "remainingQuantity": "0",
      "fee": "0.00045",
      "feeCurrency": "BTC"
    },
    "timestamp": "1776668673990"
  }
}

Extra fields versus LEG_ACTIVE: pendingPrice, filledQuantity, filledAvgPrice, fee, feeCurrency.

Message — LEG_PENDING_PRICE (lightweight)

{
  "id": "dee9afd4-e796-49a2-9cc9-c68ae394b146",
  "channel": "trade-news",
  "data": {
    "type": "LEG_PENDING_PRICE",
    "data": {
      "accountId": 10003443,
      "legId": "216172783206681210",
      "portfolioId": "144115188955249274"
    },
    "timestamp": "1776668673990"
  }
}

This is a "price dirty" hint — fires on every reprice tick with a deliberately minimal payload. The client should re-read order.change.any or GET /info to obtain the fresh price state.

Message — PORTFOLIO_FILLED

{
  "id": "fad1a292-93bf-4f83-854e-396c0b695123",
  "channel": "trade-news",
  "data": {
    "type": "PORTFOLIO_FILLED",
    "data": {
      "uid": 13554298,
      "nickname": "",
      "accountId": 10003443,
      "accountName": "rfq-test-01",
      "exchange": "DERIBIT",
      "portfolioId": "144115188955249274",
      "submitTimestamp": "1776668666388",
      "filledTimestamp": "1776668676325",
      "portfolioStatus": 2,
      "balanceTrade": true,
      "icebergTrade": false,
      "parts": 3,
      "eachLimitMs": 15000,
      "totalLimitMs": 0,
      "totalEndAction": 2,
      "totalMs": 45000,
      "fees": [
        {"fee": "0.00053717", "feeCurrency": "BTC"}
      ],
      "legs": [
        {
          "legId": "216172783206681210",
          "instrument": "BTC-25DEC26-40000-P",
          "instrumentType": "OPTION",
          "kind": 7,
          "side": 1,
          "type": 1,
          "placeQuantity": "1.5",
          "placePrice": "0.6",
          "placePriceIn": "IV",
          "placePriceLabel": 2,
          "priceSnapshot": [ /* ... */ ],
          "legStatus": 2,
          "priceCurrency": "BTC",
          "filledQuantity": "1.5",
          "filledAvgPrice": "0.155",
          "remainingQuantity": "0",
          "fee": "0.00045",
          "feeCurrency": "BTC"
        }
      ]
    },
    "timestamp": "1776668676526"
  }
}

Extra fields versus PORTFOLIO_ACTIVE: filledTimestamp, aggregate fees[], and per-leg filledQuantity / filledAvgPrice / fee / feeCurrency.

Channel: order.change.any

Full open-orders snapshot for the subscribed accountId. Pushed whenever any open order changes.

{
  "id": "30d62c6c-8ab9-4c9f-b16b-b4dd392cc710",
  "channel": "order.change.any",
  "accountId": "10003443",
  "data": [
    {
      "uid": "13554298",
      "priceLabel": 2,
      "priceOffset": 0,
      "legPlaceQuantity": 0.5,
      "legFilledQuantity": 0,
      "portfolioId": "144116212076266501",
      "portfolioType": "BALANCE",
      "legId": "216173806114194437",
      "exchange": "DERIBIT",
      "orderId": "288231400156115973",
      "exOrderId": "89509828570",
      "placeTimestamp": "1774345200637",
      "instrument": "BTC-27MAR26-71000-C",
      "instrumentType": "OPTION",
      "baseCurrency": "BTC",
      "quoteCurrency": "BTC",
      "side": 2,
      "type": 1,
      "orderStatus": 1,
      "placeQuantity": 0.1,
      "quantityIn": "BTC",
      "placePrice": 53.5,
      "placePriceIn": "IV",
      "pendingPrice": 0.02,
      "priceCurrency": "BTC",
      "filledQuantity": 0,
      "cancelledQuantity": 0,
      "remainingQuantity": 0.1,
      "timeInForce": 1
    }
  ]
}

Order Fields

Name Type Description
uid string User ID
portfolioId string Portfolio ID
legId string Leg ID
orderId string Signalplus order ID
exOrderId string Exchange-side order ID
portfolioType string BALANCE / COMMON / etc. (string here, differs from the int form used in TRADE payload)
placeTimestamp string Order place time in ms
side int 1=BUY / 2=SELL
type int 1=LIMIT / 2=MARKET
orderStatus int 1=ACTIVE/PENDING, 2=FILLED, 3=CANCELLED, 4=CANCELLING
cancelType int Present only when cancelled: 1=by system, 2=by user, 5=by no dynamic price
cancelledReason string Human-readable cancel reason
placeQuantity decimal Placed quantity
filledQuantity decimal Filled quantity
cancelledQuantity decimal Cancelled quantity
remainingQuantity decimal Unfilled quantity
priceLabel int 1=MID / 2=MARK / 3=MODEL / 4=BID_ASK
priceOffset decimal Offset from dynamic reference
placePrice decimal Placed price
pendingPrice decimal Current reprice target
placePriceIn string USD / USDT / BTC / IV / ...
priceCurrency string Price currency
timeInForce int 1=GTC / 2=IOC / 3=FOK / 4=GTD
legPlaceQuantity decimal Leg-level roll-up across orders in the same leg
legFilledQuantity decimal Leg-level filled roll-up

Semantics: this channel always pushes the full open-orders set for the accountId (not delta). On reconnect, the first push after re-subscribe serves as the fresh snapshot. The client should rebuild UI state from the payload rather than patching.

Reconnect Guidelines

Topic Recommendation
Heartbeat Send {"op":"heartbeat"} every 10–30 seconds. Server timeout is 3 minutes
Disconnect Reconnect after 1–3 s backoff. All subscriptions are lost (connection-scoped); the client must re-subscribe
State reconciliation After re-subscribing to order.change.any, the first push is the fresh snapshot — no need to call /info separately
Authoritative state For authoritative state, call GET /portfolios/info after each reconnect; WS push is best-effort

results matching ""

    No results matching ""