Quick Start
- Fubon Futures Market Data WebSocket API provides real-time futures subscriptions.
- Supports
SpeedandNormalmodes. - Login is required before subscribing to channels.
| Item | Details |
|---|---|
| Interface | WebSocket API |
| Market | Taiwan futures / options |
| Modes | Speed / Normal |
| SDK | Python / Node.js / C# |
| Subscription | Connect then subscribe to channels |
Fubon Futures Market Data WebSocket API provides real-time futures market data for Taiwan.
Using SDK
Fubon Futures Market Data WebSocket API provides Python, Node.js, and C# SDKs. You can access the WebSocket API through the following methods:
And subscribe webSocket callback method to receive the callback messages below.
WebSocket API provides the low-latency Speed mode and the multi-information Normal mode.
The futures and options market data WebSocket offers two streaming versions, v1.0 and v1.1. When no version is specified, the connection resolves to v1.1. Trial-matching messages (isTrial) on the trades and books channels are pushed only over v1.1; pin the connection to v1.0 and you will not receive them. The aggregates channel pushes updates during the trial-matching session on both versions. Stock market data is served on v1.0 only. Available since v2.2.9.
- Python
- Node.js
- C#
from fubon_neo.sdk import FubonSDK, Mode
def handle_message(message):
print(message)
sdk = FubonSDK()
accounts = sdk.login("Your ID", "Your password", "Your cert path", "Your cert password")
sdk.init_realtime()
# Offer MarketData Mode , Default : Speed
# sdk.init_realtime(Mode.Speed) or sdk.init_realtime(Mode.Normal)
# Offer FutOpt streaming version , Default : v1.1 ; v1.0 does not deliver trial-matching messages on trades/books
# sdk.init_realtime(Mode.Speed, version={"futopt": "v1.0"})
# After connecting, sdk.marketdata.websocket_client.futopt.url reports the version actually resolved
futopt = sdk.marketdata.websocket_client.futopt
futopt.on('message', handle_message)
futopt.connect()
const { FubonSDK, Mode } = require('fubon-neo');
const sdk = new FubonSDK();
const accounts = sdk.login("Your ID", "Your password", "Your cert path", "Your cert password");
sdk.initRealtime();
// Offer MarketData Mode , Default : Speed
// sdk.initRealtime(Mode.Speed) or sdk.initRealtime(Mode.Normal)
// Offer FutOpt streaming version , Default : v1.1 ; v1.0 does not deliver trial-matching messages on trades/books
// sdk.initRealtime(Mode.Speed, { futopt: 'v1.0' });
// After connecting, sdk.marketdata.webSocketClient.futopt.url reports the version actually resolved
const futopt = sdk.marketdata.webSocketClient.futopt;
futopt.connect()
futopt.on("message", (message) => {
const data = JSON.parse(message);
console.log(data);
});
using FubonNeo.Sdk;
using FugleMarketData.WebsocketModels;
var sdk = new FubonSDK();
var result = sdk.Login("Your ID", "Your password", "Your cert path", "Your cert password");
sdk.InitRealtime();
// Offer MarketData Mode , Default : Speed
// sdk.InitRealtime(Mode.Speed) or sdk.InitRealtime(Mode.Normal)
// Offer FutOpt streaming version , Default : v1.1 ; v1.0 does not deliver trial-matching messages on trades/books
// sdk.InitRealtime(Mode.Speed, new FugleMarketData.WebsocketClient.WebsocketVersionOptions { FutOpt = "v1.0" });
var futopt = sdk.MarketData.WebSocketClient.FutureOption;
futopt.OnMessage += (msg) => Console.WriteLine($"receive: {msg}");
await futopt.Connect();
Authenticated
When the verification is successful, you will receive the following message:
{
"event": "authenticated",
"data": {
"message": "Authenticated successfully"
}
}
When the verification is failed, you will receive the following message:
{
"event": "error",
"data": {
"message": "Invalid authentication credentials"
}
}
Heartbeat
Every 30 seconds, the WebSocket server will send out a heartbeat message:
{
"event": "heartbeat",
"data": {
"time": "<Timestamp>"
}
}
Ping/Pong
Every 30 seconds, the SDK will send the following JSON-formatted message to the WebSocket Server, or follow the below example (where state is optional):
- Python
- Node.js
- C#
futopt.ping('<ANY>')
futopt.ping({state:'<ANY>'});
futopt.Ping("<ANY>");
The WebSocket Server will respond with the following message (if ping was sent, the state field will not be present):
{
"event": "pong",
"data": {
"time": "<TIMESTAMP>",
"state": "<ANY>"
}
}
Channels
The Fubon Futures Market Data WebSocket API currently provides the following subscribable channels:
trades- Subscribe to the latest future trade informationbooks- Subscribe to the latest top five bid and ask information for stocksaggregates- Subscribe to aggregated market data for futures/options contracts
Futures/Options Market Data at a Glance
The following market data features are available since v2.2.9:
- Continuous-month quotes: Subscribe with a
{ROOT}1!/2!/3!nearby-month alias; see Continuous Month Aliases below. - Spread-contract quotes: First retrieve spread contract symbols with
isSpread=truefrom Intraday Tickers, then use the returnedsymbolin the WebSocket subscription; see Subscribing to Spread Contracts below. - Derived best-level quotes:
derivedBid/derivedAskin thebookschannel provide a derived best quote level, primarily for spread contracts. - Trial-matching information:
isTrialinbooksandlastTrialinaggregatesdisclose information for the trial-matching phase.
Subscribe Channel
Subscribe to a channel, send following example to the WebSocket Server:
- Python
- Node.js
- C#
futopt.subscribe({
"channel" : "<CHANNEL_NAME>",
"symbol" : "<SYMBOL_ID>"
#"afterHours" : True Subscribe after-hours
})
futopt.subscribe({
channel: '<CHANNEL_NAME>',
symbol: '<SYMBOL_ID>',
//afterHours : true Subscribe after-hours
});
futopt.Subscribe(FutureOptionChannel.<CHANNEL_NAME>,"<SYMBOL_ID>");
//futopt.Subscribe(FutureOptionChannel.Trades,new FutureOptionParams{Symbol="<SYMBOL_ID>", AfterHours = true}); Subscribe after-hours
After a successful subscription, you will receive the following event response:
{
"event": "subscribed",
"data": {
"id": "<CHANNEL_ID>",
"channel": "<CHANNEL_NAME>",
"symbol": "<SYMBOL_ID>"
}
}
Supports the subscription of multiple stocks within the same channel:
- Python
- Node.js
- C#
futopt.subscribe({
"channel" : "<CHANNEL_NAME>",
"symbols" : ["<SYMBOL_ID>","<SYMBOL_ID>"]
#"afterHours" : True Subscribe after-hours
})
futopt.subscribe({
channel: '<CHANNEL_NAME>',
symbols: ['<SYMBOL_ID>','<SYMBOL_ID>']
//afterHours : true Subscribe after-hours
});
futopt.Subscribe(FutureOptionChannel.<CHANNEL_NAME>,"<SYMBOL_ID>","<SYMBOL_ID>");
//futopt.Subscribe(FutureOptionChannel.Trades, new FutureOptionParams{Symbols = new List<string>{"<SYMBOL_ID>", "<SYMBOL_ID>"}, AfterHours = true}); Subscribe after-hours
After a successful subscription, you will receive the following event response:
{
"event": "subscribed",
"data": [
{
"id": "<CHANNEL_ID>",
"channel": "<CHANNEL_NAME>",
"symbol": "<SYMBOL_ID_1>"
},
{
"id": "<CHANNEL_ID>",
"channel": "<CHANNEL_NAME>",
"symbol": "<SYMBOL_ID_2>"
}
]
}
Continuous Month Aliases
In addition to a concrete contract symbol (e.g. TXFG6), the symbol used when subscribing can also be a continuous month alias: {ROOT}1!/2!/3!, which subscribes to the 1st/2nd/3rd nearby month contract (e.g. TXF1! for the nearest-month Taiwan stock index future). The alias is resolved to the matching concrete contract once, when the subscription is sent; the symbol in data events is the resolved concrete contract symbol. The subscription stays bound to that contract afterwards and does not roll over after settlement — on a long-lived connection, resubscribe after the roll (unsubscribe then subscribe) or subscribe with concrete contract symbols instead. The Web API is unaffected: every request resolves the alias again. See Web API — Contract Symbols and Continuous-Month Aliases.
- Python
- Node.js
- C#
futopt.subscribe({
"channel": "trades",
"symbol": "TXF1!" # Continuous month alias, subscribes to the nearest-month Taiwan stock index future
#"afterHours" : True Subscribe after-hours
})
futopt.subscribe({
channel: "trades",
symbol: "TXF1!" // Continuous month alias, subscribes to the nearest-month Taiwan stock index future
//afterHours : true Subscribe after-hours
});
futopt.Subscribe(FutureOptionChannel.Trades, "TXF1!"); // Continuous month alias, subscribes to the nearest-month Taiwan stock index future
Subscribing to Spread Contracts
Calendar spread contracts (e.g. TXFJ6/K6) are subscribed with the same symbol field; pass the symbol as-is and the connection will start receiving that spread contract's data once subscribed. See Spread Contracts for the symbol format.
- Python
- Node.js
- C#
futopt.subscribe({
"channel": "books",
"symbol": "TXFJ6/K6"
})
futopt.subscribe({
channel: "books",
symbol: "TXFJ6/K6"
});
futopt.Subscribe(FutureOptionChannel.Books, "TXFJ6/K6");
Unsubscribe
Unsubscribe from a subscribed channel, please send the following example to WebSocket Server:
- Python
- Node.js
- C#
futopt.unsubscribe({
'id':'<CHANNEL_ID>'
})
futopt.unsubscribe({
id : '<CHANNEL_ID>'
});
futopt.Unsubscribe("<CHANNEL_ID>");
After a successful unsubscription, you will receive the following event response:
{
"event": "unsubscribed",
"data": {
"id": "<CHANNEL_ID>",
"channel" : "<CHANNEL_NAME>",
"symbol" : "<SYMBOL_ID>"
}
}
Supports the unsubscription from multiple channels:
- Python
- Node.js
- C#
futopt.unsubscribe({
'ids':['<CHANNEL_ID>','<CHANNEL_ID>']
})
futopt.unsubscribe({
ids : ['<CHANNEL_ID>','<CHANNEL_ID>']
});
futopt.Unsubscribe("<CHANNEL_ID>","<CHANNEL_ID>");
After a successful unsubscription, you will receive the following event response:
{
"event": "unsubscribed",
"data": [
{
"id": "<CHANNEL_ID_1>",
"channel" : "<CHANNEL_NAME>",
"symbol" : "<SYMBOL_ID>"
},
{
"id": "<CHANNEL_ID_2>",
"channel" : "<CHANNEL_NAME>",
"symbol" : "<SYMBOL_ID>"
}
]
}
Subscriptions
To list the channels currently subscribed on this connection, use the following example:
- Python
- Node.js
futopt.subscriptions()
futopt.subscriptions();
You will receive the following event response:
{
"event": "subscriptions",
"data": [
{
"id": "<CHANNEL_ID>",
"channel": "<CHANNEL_NAME>",
"symbol": "<SYMBOL_ID>"
}
]
}
Error Handle
When there is an anomaly in the WebSocket callback you subscribed to or processed, you can handle error messages as follows.
The disconnect event fires in two cases: the server closes the connection, or the connection health check times out — when no message of any kind has arrived from the server for roughly 60 seconds, the SDK closes the connection itself and emits this event. How a timeout is reported in each language is noted under its example.
- Python
- Node.js
- C#
def handle_connect():
print('market data connected')
def handle_disconnect(code, message, *reason): # a health-check-timeout disconnect passes one extra argument
print(f'market data disconnect: {code}, {message}')
def handle_error(error):
print(f'market data error: {error}')
futopt.on("connect", handle_connect)
futopt.on("disconnect", handle_disconnect)
futopt.on("error", handle_error)
On a health-check timeout, disconnect passes one extra argument {'reason': 'health-check-timeout'}; *reason in the handler catches it.
futopt.on("connect", () => {
console.log("market data connected");
});
futopt.on("disconnect", (event, reason) => {
console.log("market data disconnect:", event.code, reason);
});
futopt.on("error", (error) => {
console.log("market data error:", error);
});
On a health-check timeout, the second argument of disconnect is { reason: 'health-check-timeout' }.
futopt.OnConnected += (connmsg) => Console.WriteLine($"Connect: {connmsg}");
futopt.OnDisconnected += (disconmsg) => Console.WriteLine($"Disconnect: {disconmsg}");
futopt.OnError += (errmsg) => Console.WriteLine($"handle error: {errmsg}");
Enable the health check with the second argument of Connect (Connect(5000, true); the first argument is the authentication timeout in milliseconds). On a timeout the OnDisconnected message is health-check-timeout; without it, a silently dead connection never triggers OnDisconnected.
Reconnection
After a disconnection the SDK neither reconnects on its own nor keeps your subscriptions; your program has to reconnect when it receives the disconnect event and send every subscription again. The following example continues from the login and connection code above: on a disconnect event it reconnects and resubscribes in the background, with a growing delay between attempts (2, 4, 8, 16, 30 seconds) and a limit of 5 attempts, so that a network that has not recovered yet is not flooded with reconnects. Keep one entry per channel in the subscription list and subscribe to several symbols at once with symbols, so the number of subscribe calls after a reconnect equals the number of channels rather than the number of symbols.
- Python
- Node.js
- C#
# continue after futopt = sdk.marketdata.websocket_client.futopt; replaces the original futopt.connect()
import threading
import time
SUBSCRIPTIONS = [ # one list shared by the first subscription and every resubscription; one entry per channel, several symbols via symbols
{'channel': 'trades', 'symbols': ['TXF1!', 'MXF1!'], 'afterHours': True},
]
reconnect_needed = threading.Event()
def handle_disconnect(code, message, *reason): # a health-check-timeout disconnect passes one extra argument; *reason catches it
print(f'market data disconnect: {code}, {message}')
reconnect_needed.set() # notify only; do not reconnect here
def subscribe_all():
for params in SUBSCRIPTIONS:
futopt.subscribe(params)
def connect_with_timeout(seconds=10):
# connect() never returns while the network is down; run it in a thread with a timeout
result = {}
def run():
try:
futopt.connect()
except Exception as error:
result['error'] = error
worker = threading.Thread(target=run, daemon=True)
worker.start()
worker.join(seconds)
if worker.is_alive():
raise TimeoutError('connect timeout')
if 'error' in result:
raise result['error']
def reconnect_loop():
retries = 0
while reconnect_needed.wait():
reconnect_needed.clear()
retries += 1
if retries > 5:
print('Reconnect failed 5 times, please check the network')
return
time.sleep(min(2 ** retries, 30)) # wait 2, 4, 8, 16, 30 seconds before retrying
try:
futopt.disconnect() # reset the connection state first, otherwise connect() will not reconnect
connect_with_timeout()
subscribe_all()
retries = 0
print('Reconnected and resubscribed')
except Exception as error:
print(f'Reconnect failed: {error}')
reconnect_needed.set()
futopt.on('disconnect', handle_disconnect)
threading.Thread(target=reconnect_loop, daemon=True).start()
futopt.connect()
subscribe_all()
// continue after sdk.login(...); initRealtime and event binding move into connectAndSubscribe()
const SUBSCRIPTIONS = [ // one list shared by the first subscription and every resubscription; one entry per channel, several symbols via symbols
{ channel: 'trades', symbols: ['TXF1!', 'MXF1!'], afterHours: true },
];
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let futopt;
let reconnecting = false;
function handleMessage(message) {
console.log(JSON.parse(message));
}
async function connectAndSubscribe() {
sdk.initRealtime(); // rebuild the market data client on every reconnect; events must be bound again
futopt = sdk.marketdata.webSocketClient.futopt;
const client = futopt;
client.on('message', handleMessage);
client.on('disconnect', (event, reason) => handleDisconnect(client, event, reason));
await Promise.race([ // connect() never settles while the network is down; add a timeout
client.connect(),
sleep(10000).then(() => { throw new Error('connect timeout'); }),
]);
for (const params of SUBSCRIPTIONS) client.subscribe(params);
}
async function handleDisconnect(client, event, reason) {
console.log('market data disconnect:', event.code, reason);
if (client !== futopt || reconnecting) return; // skip events from an old client object, or while a reconnect is already running
reconnecting = true;
try {
for (let retries = 1; retries <= 5; retries++) {
await sleep(Math.min(2 ** retries, 30) * 1000); // wait 2, 4, 8, 16, 30 seconds before retrying
try {
futopt.disconnect(); // close the old connection and stop its timer
await connectAndSubscribe();
console.log('Reconnected and resubscribed');
return;
} catch (error) {
console.log(`Reconnect failed: ${error.message}`);
}
}
console.log('Reconnect failed 5 times, please check the network');
} finally {
reconnecting = false;
}
}
await connectAndSubscribe();
// continue after var futopt = sdk.MarketData.WebSocketClient.FutureOption; replaces the original await futopt.Connect()
var subscriptions = new[] { (FutureOptionChannel.Trades, new FutureOptionParams { Symbols = new List<string> { "TXF1!", "MXF1!" }, AfterHours = true }) }; // one list shared by the first subscription and every resubscription; one entry per channel, several symbols via Symbols
var reconnecting = 0;
async Task SubscribeAll()
{
foreach (var (channel, param) in subscriptions)
await futopt.Subscribe(channel, param);
}
futopt.OnDisconnected += (msg) =>
{
Console.WriteLine($"market data disconnect: {msg}");
if (Interlocked.Exchange(ref reconnecting, 1) == 1) return; // one disconnection may trigger several notifications; keep a single reconnect flow
_ = Task.Run(async () =>
{
try
{
for (var retries = 1; retries <= 5; retries++)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Min(Math.Pow(2, retries), 30))); // wait 2, 4, 8, 16, 30 seconds before retrying
try
{
await futopt.Connect(5000, true); // 5-second authentication timeout; enable the connection health check
if (!futopt.IsConnected) continue; // Connect does not throw on authentication timeout; check the state yourself
await SubscribeAll();
Console.WriteLine("Reconnected and resubscribed");
return;
}
catch (Exception ex)
{
Console.WriteLine($"Reconnect failed: {ex.Message}");
}
}
Console.WriteLine("Reconnect failed 5 times, please check the network");
}
finally
{
Interlocked.Exchange(ref reconnecting, 0);
}
});
};
await futopt.Connect(5000, true); // enable the health check so a silently dead connection also triggers a disconnect notification
await SubscribeAll();