10. WebSocket Market Data
Receive trade prices, quotes, and foreign room data in real-time via WebSocket
Objective
Receive real-time tick data (trade prices, order book quotes, foreign room) via WebSocket. No OTP required for market data.
Flow
Client ↔ Streaming API ↔ Market Data Event- Open the WebSocket connection with a valid token (no OTP needed)
- Subscribe by symbol (
subscribe_symbol), by the constituents of an index (subscribe_index) and by the index's own ticks (subscribe_index_ticks) - The server pushes events on new trades or quote changes
- The
on_datacallback dispatches by type:IndexTickMessage,TradeMessage,QuoteMessage,ForeignRoomMessage - On disconnect, the SDK reconnects
Message Types
| Type | Description |
|---|---|
TradeMessage | Trade price, volume, buy/sell side |
QuoteMessage | Bid/ask order book |
ForeignRoomMessage | Foreign room remaining |
IndexTickMessage | Tick of the index itself (points, change), received via subscribe_index_ticks |
HeartbeatMessage | Server keepalive |
Since SDK 3.3.0,
subscribe_index(["VN30"])sendstrade/quote/room.<code>@*, so it receives data for the index constituents (TradeMessage,QuoteMessage...), not the index tick. To get the tick of the index itself (e.g.VNINDEX), usesubscribe_index_ticks. See also Advanced WebSocket.
Sample Code — Sync
from ssi_sdk import Auth, Stream, Config
from auth_helper import ensure_auth
from ssi_sdk.models.streaming import (
TradeMessage, QuoteMessage, ForeignRoomMessage, IndexTickMessage,
)
config = Config(
client_id="<your_client_id>",
api_key="<your_api_key>",
api_secret="<your_api_secret>",
private_key="<your_private_key>",
)
def on_market_data(msg):
if isinstance(msg, IndexTickMessage):
# Tick of the index itself (subscribe_index_ticks), NOT a constituent tick
print(f" [INDEX] {msg.symbol} | Value: {msg.value} | Change: {msg.change} ({msg.change_percent}%)")
elif isinstance(msg, TradeMessage):
print(f" [TRADE] {msg.symbol} | Price: {msg.price} | Qty: {msg.quantity}")
elif isinstance(msg, QuoteMessage):
print(f" [QUOTE] {msg.symbol} | Bid: {msg.bid_prices[:3]} | Ask: {msg.ask_prices[:3]}")
elif isinstance(msg, ForeignRoomMessage):
print(f" [ROOM] {msg.symbol} | Room: {msg.current_room}/{msg.total_room}")
with Auth(config) as auth:
ensure_auth(auth)
with Stream(auth) as stream:
stream.streaming.connect()
stream.streaming.on_data = on_market_data
stream.streaming.subscribe_symbol(["SSI", "HPG", "VNM"])
# Since SDK 3.3.0, subscribe_index returns the CONSTITUENTS' data (e.g. VN30)
stream.streaming.subscribe_index(["VN30"])
# For the index's own tick (IndexTickMessage) use subscribe_index_ticks
stream.streaming.subscribe_index_ticks(["VNINDEX"])
try:
stream.streaming.wait(timeout_ms=300_000) # 5 minutes
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 TradeMessage, QuoteMessage
config = Config(...)
async def main():
async with AsyncAuth(config) as auth:
await ensure_auth_async(auth)
async with AsyncStream(auth) as stream:
await stream.streaming.connect()
stream.streaming.on_data = lambda msg: print(f" {msg}")
await stream.streaming.subscribe_symbol(["SSI", "HPG"])
await stream.streaming.subscribe_index(["VN30"]) # constituents of VN30
await stream.streaming.subscribe_index_ticks(["VNINDEX"]) # the index's own tick
await stream.streaming.wait(timeout_ms=300_000)
asyncio.run(main())Notes
- No OTP required for market data streaming.
- SDK handles reconnect automatically.
- Use
wait(timeout_ms=...)to limit listening duration. Since SDK 3.3.0 the unit is milliseconds; the oldtimeout=(seconds) was removed and raisesTypeError. subscribe_indexreceives constituents' data;subscribe_index_ticksreceives the index's own tick. For board, index summary, session flag, candles and connection events see Advanced WebSocket.