11. WebSocket Trading

Receive real-time order status and portfolio updates via WebSocket

Objective

Receive instant updates on order status and portfolio changes via WebSocket. Requires OTP during authentication.

Flow

Client ↔ Streaming API ↔ Trading Event
  1. Open the WebSocket connection with a valid token (OTP required)
  2. Subscribe with subscribe_order_status(account_no) and subscribe_portfolio(account_no)
  3. The server pushes events when an order status or the portfolio changes
  4. The on_trading callback dispatches by type: OrderStatusMessage, OrderMatchMessage, PortfolioMessage
  5. On disconnect, the SDK reconnects

Message Types

TypeDescription
OrderStatusMessageOrder status (symbol, side, order_id, status, filled_quantity)
OrderMatchMessageOne fill (symbol, side, order_id, match_price, match_qty). Same topic as order status; the SDK splits it into its own message
PortfolioMessagePositions (long_qty, short_qty, net, floating_pl...). Only for derivative accounts
HeartbeatMessageServer keepalive

PortfolioMessage.total_asset, cash_balance and stock_value are deprecated: the server never sends them, so they are always None. Use the position fields (long_qty, short_qty, net, floating_pl). For equity account assets / buying power, call REST (Tutorial 5).

The dedicated on_order, on_order_match and on_portfolio callbacks are covered in Advanced WebSocket.

Sample Code — Sync

python/sample_11_websocket_trading.py
from ssi_sdk import Auth, Stream, Config
from auth_helper import ensure_auth
from helpers import enum_value
from ssi_sdk.models.streaming import OrderStatusMessage, OrderMatchMessage, PortfolioMessage

config = Config(
    client_id="<your_client_id>",
    api_key="<your_api_key>",
    api_secret="<your_api_secret>",
    private_key="<your_private_key>",
)
ACCOUNT_NO = "<your_account_no>"

def on_trading_event(msg):
    if isinstance(msg, OrderStatusMessage):
        print(f"  [ORDER] {msg.symbol} | {enum_value(msg.status)} | {msg.filled_quantity}/{msg.quantity}")
    elif isinstance(msg, OrderMatchMessage):
        # Fill: same topic as order status, the SDK splits it into OrderMatchMessage
        print(f"  [MATCH] {msg.symbol} | {msg.match_price} x {msg.match_qty}")
    elif isinstance(msg, PortfolioMessage):
        # Derivative accounts only; total_asset/cash_balance/stock_value are deprecated (always None)
        print(f"  [PORTFOLIO] {msg.symbol} | Net: {msg.net}")

with Auth(config) as auth:
    ensure_auth(auth, otp="<your_otp>")
    with Stream(auth) as stream:
        stream.streaming.connect()
        stream.streaming.on_trading = on_trading_event
        stream.streaming.subscribe_order_status(ACCOUNT_NO)
        try:
            stream.streaming.wait(timeout_ms=300_000)  # 5 minutes (milliseconds)
        except KeyboardInterrupt:
            pass

Sample Code — Async

python/sample_11_websocket_trading_async.py
import asyncio
from ssi_sdk import AsyncAuth, AsyncStream, Config
from auth_helper import ensure_auth_async
from ssi_sdk.models.streaming import OrderStatusMessage, PortfolioMessage

config = Config(...)
ACCOUNT_NO = "<your_account_no>"

async def main():
    async with AsyncAuth(config) as auth:
        await ensure_auth_async(auth, otp="<your_otp>")
        async with AsyncStream(auth) as stream:
            await stream.streaming.connect()
            stream.streaming.on_trading = lambda msg: print(f"  {msg}")
            await stream.streaming.subscribe_order_status(ACCOUNT_NO)
            await stream.streaming.wait(timeout_ms=300_000)

asyncio.run(main())

Polling vs WebSocket

Polling (Sample 8)WebSocket (Sample 11)
LatencyPer poll cycle (3-5s)Near real-time (<100ms)
TrafficRepeated requestsSingle persistent connection
ComplexitySimpleNeeds reconnect handling
Use caseQuick checksProduction monitoring

On this page