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- Open the WebSocket connection with a valid token (OTP required)
- Subscribe with
subscribe_order_status(account_no)andsubscribe_portfolio(account_no) - The server pushes events when an order status or the portfolio changes
- The
on_tradingcallback dispatches by type:OrderStatusMessage,OrderMatchMessage,PortfolioMessage - On disconnect, the SDK reconnects
Message Types
| Type | Description |
|---|---|
OrderStatusMessage | Order status (symbol, side, order_id, status, filled_quantity) |
OrderMatchMessage | One fill (symbol, side, order_id, match_price, match_qty). Same topic as order status; the SDK splits it into its own message |
PortfolioMessage | Positions (long_qty, short_qty, net, floating_pl...). Only for derivative accounts |
HeartbeatMessage | Server keepalive |
PortfolioMessage.total_asset,cash_balanceandstock_valueare deprecated: the server never sends them, so they are alwaysNone. 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_matchandon_portfoliocallbacks are covered in Advanced WebSocket.
Sample Code — Sync
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:
passSample Code — Async
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) | |
|---|---|---|
| Latency | Per poll cycle (3-5s) | Near real-time (<100ms) |
| Traffic | Repeated requests | Single persistent connection |
| Complexity | Simple | Needs reconnect handling |
| Use case | Quick checks | Production monitoring |