Communication Between Live Trading Strategies
Overview
The inter-strategy communication feature allows different live trading strategies to share data and synchronize state with one another. Through a channel mechanism, one live strategy can broadcast its own state data to other live strategies, enabling data communication across strategies, across managers, and across servers.
Core Concepts
-
Channel: Every live strategy owns an independent channel, and the channel ID is the live strategy ID
-
Broadcaster: A live strategy that publishes data on a channel using the
SetChannelData()function -
Subscriber: A live strategy that subscribes to another live strategy's channel data using the
GetChannelData()function -
State Overwrite: A channel retains only the latest state; new data overwrites old data rather than using a message queue mechanism
Key Features
-
Non-blocking Communication: All function calls are non-blocking and will not affect the strategy's main workflow
-
Cross-platform Support: Supports data transmission across strategies, across managers, and across servers
-
Multi-channel Subscription: A single live strategy can subscribe to the channels of multiple different live strategies simultaneously
-
Flexible Data Format: Supports any JSON-serializable data structure
Use Cases
-
Master-slave Strategy Coordination: The master strategy analyzes the market and broadcasts signals, while slave strategies receive the signals and execute trades
-
Multi-account Synchronization: Synchronize trading signals and position information across multiple trading accounts
-
Strategy Monitoring: Broadcast strategy running status, which a monitoring live strategy subscribes to for display or alerting
-
Data Sharing: Share results such as market analysis and indicator calculations to avoid redundant computation
Basic Usage
Examples
-
Broadcaster Example - Publishing Market Data
javascriptfunction main() { var updateId = 0 var robotId = _G() // Get the current live bot ID while(true) { // Fetch market data var ticker = exchange.GetTicker("BTC_USDT") if (!ticker) { Sleep(5000) continue } // Prepare the channel state data var channelState = { robotId: robotId, updateId: ++updateId, timestamp: Date.now(), symbol: "BTC_USDT", lastPrice: ticker.Last, volume: ticker.Volume, high: ticker.High, low: ticker.Low } // Publish the latest state on the channel (overwrites the old state) SetChannelData(channelState) // Display the current channel state LogStatus("Channel Broadcaster [Bot ID: " + robotId + "]\n" + "Update ID: #" + channelState.updateId + "\n" + "Time: " + _D(channelState.timestamp) + "\n" + "Symbol: " + channelState.symbol + "\n" + "Last Price: $" + channelState.lastPrice.toFixed(2)) Sleep(60000) // Update the channel state once per minute } }pythondef main(): updateId = 0 robotId = _G() # Get the current live bot ID while True: # Fetch market data ticker = exchange.GetTicker("BTC_USDT") if not ticker: Sleep(5000) continue # Prepare the channel state data channelState = { "robotId": robotId, "updateId": updateId + 1, "timestamp": time.time() * 1000, "symbol": "BTC_USDT", "lastPrice": ticker["Last"], "volume": ticker["Volume"], "high": ticker["High"], "low": ticker["Low"] } updateId += 1 # Publish the latest state on the channel (overwrites the old state) SetChannelData(channelState) # Display the current channel state LogStatus("Channel Broadcaster [Bot ID: {}]\n".format(robotId) + "Update ID: #{}\n".format(channelState["updateId"]) + "Time: {}\n".format(_D(channelState["timestamp"])) + "Last Price: ${:.2f}".format(channelState["lastPrice"])) Sleep(60000) # Update the channel state once per minuterustfn main() { let mut updateId = 0; let robotId = _G!(); // Get the current live bot ID loop { // Fetch market data let ticker = match exchange.GetTicker("BTC_USDT") { Ok(t) => t, Err(_) => { Sleep(5000); continue; } }; // Prepare the channel state data // Rust's SetChannelData only accepts a string argument, so use format! to build the JSON text updateId += 1; let timestamp = Unix() * 1000; let channelState = format!( r#"{{"robotId": {}, "updateId": {}, "timestamp": {}, "symbol": "BTC_USDT", "lastPrice": {}, "volume": {}, "high": {}, "low": {}}}"#, robotId, updateId, timestamp, ticker.Last, ticker.Volume, ticker.High, ticker.Low ); // Publish the latest state on the channel (overwrites the old state) SetChannelData(&channelState); // Display the current channel state LogStatus!(format!( "Channel Broadcaster [Bot ID: {}]\nUpdate ID: #{}\nTime: {}\nSymbol: BTC_USDT\nLast Price: ${:.2}", robotId, updateId, _D(timestamp), ticker.Last )); Sleep(60000); // Update the channel state once per minute } }c++ -
Subscriber Example - Subscribe to Multiple Channels
javascriptfunction main() { // Get the IDs of the two channels to subscribe to (please modify according to your actual situation) var channelId1 = "632799" // Live trading ID of channel 1 var channelId2 = "632800" // Live trading ID of channel 2 while(true) { // Get the current state of channel 1 var state1 = GetChannelData(channelId1) // Get the current state of channel 2 var state2 = GetChannelData(channelId2) // Build the status display message var statusMsg = "Channel Subscriber - Current Subscription Status\n\n" // Display the status of channel 1 statusMsg += "═══ Channel1 [" + channelId1 + "] ═══\n" if (state1 !== null) { statusMsg += "Update ID: #" + state1.updateId + "\n" statusMsg += "Time: " + _D(state1.timestamp) + "\n" statusMsg += "Symbol: " + state1.symbol + "\n" statusMsg += "Last Price: $" + state1.lastPrice.toFixed(2) + "\n" } else { statusMsg += "Status: Waiting... (first call returns null)\n" } statusMsg += "\n" // Display the status of channel 2 statusMsg += "═══ Channel2 [" + channelId2 + "] ═══\n" if (state2 !== null) { statusMsg += "Update ID: #" + state2.updateId + "\n" statusMsg += "Time: " + _D(state2.timestamp) + "\n" statusMsg += "Last Price: $" + state2.lastPrice.toFixed(2) + "\n" } else { statusMsg += "Status: Waiting... (first call returns null)\n" } LogStatus(statusMsg) Sleep(5000) // Fetch channel data every 5 seconds } }pythondef main(): # Get the IDs of the two channels to subscribe to (please modify according to your actual situation) channelId1 = "632799" # Live trading ID of channel 1 channelId2 = "632800" # Live trading ID of channel 2 while True: # Get the current state of channel 1 state1 = GetChannelData(channelId1) # Get the current state of channel 2 state2 = GetChannelData(channelId2) # Build the status display message statusMsg = "Channel Subscriber - Current Subscription Status\n\n" # Display the status of channel 1 statusMsg += "═══ Channel1 [{}] ═══\n".format(channelId1) if state1 is not None: statusMsg += "Update ID: #{}\n".format(state1["updateId"]) statusMsg += "Time: {}\n".format(_D(state1["timestamp"])) statusMsg += "Last Price: ${:.2f}\n".format(state1["lastPrice"]) else: statusMsg += "Status: Waiting... (first call returns None)\n" statusMsg += "\n" # Display the status of channel 2 statusMsg += "═══ Channel2 [{}] ═══\n".format(channelId2) if state2 is not None: statusMsg += "Update ID: #{}\n".format(state2["updateId"]) statusMsg += "Time: {}\n".format(_D(state2["timestamp"])) statusMsg += "Last Price: ${:.2f}\n".format(state2["lastPrice"]) else: statusMsg += "Status: Waiting... (first call returns None)\n" LogStatus(statusMsg) Sleep(5000) # Fetch channel data every 5 secondsrustfn main() { // Rust's GetChannelData() function does not accept a channel ID parameter and cannot subscribe to channels of other live trading bots, // it can only read the latest data of the current live trading bot's own channel (i.e. the data this bot publishes via SetChannelData()) loop { // Read the current state of this bot's channel let state = GetChannelData(); // Build the status display message let mut statusMsg = String::from("Channel Subscriber - Current Subscription Status\n\n"); if !state.is_null() { statusMsg += &format!("Update ID: #{}\n", state["updateId"].as_f64().unwrap_or(0.0)); statusMsg += &format!("Time: {}\n", _D(state["timestamp"].as_i64().unwrap_or(0))); statusMsg += &format!("Symbol: {}\n", state["symbol"].as_str().unwrap_or("")); statusMsg += &format!("Last Price: ${:.2}\n", state["lastPrice"].as_f64().unwrap_or(0.0)); } else { statusMsg += "Status: Waiting... (first call returns null)\n"; } LogStatus!(statusMsg); Sleep(5000); // Read channel data every 5 seconds } }c++ -
Practical Application Scenarios
Scenario 1: Master-Slave Strategy Collaborative Trading
Master Strategy (Signal Broadcasting End)
javascriptfunction main() { var robotId = _G() Log("Main strategy started, Bot ID:", robotId) while(true) { // Analyze market conditions and generate trading signals var records = exchange.GetRecords("BTC_USDT") if (!records || records.length < 20) { Sleep(5000) continue } // Simple moving average crossover strategy var ma5 = TA.MA(records, 5) var ma20 = TA.MA(records, 20) var signal = "HOLD" if (ma5[ma5.length-1] > ma20[ma20.length-1] && ma5[ma5.length-2] <= ma20[ma20.length-2]) { signal = "BUY" } else if (ma5[ma5.length-1] < ma20[ma20.length-1] && ma5[ma5.length-2] >= ma20[ma20.length-2]) { signal = "SELL" } // Broadcast the trading signal var signalData = { timestamp: Date.now(), symbol: "BTC_USDT", signal: signal, price: records[records.length-1].Close, ma5: ma5[ma5.length-1], ma20: ma20[ma20.length-1] } SetChannelData(signalData) LogStatus("Main Strategy - Signal Broadcast\n" + "Signal: " + signal + "\n" + "Price: $" + signalData.price.toFixed(2) + "\n" + "MA5: " + signalData.ma5.toFixed(2) + "\n" + "MA20: " + signalData.ma20.toFixed(2)) Sleep(60000) } }pythondef main(): robotId = _G() Log("Main strategy started, Bot ID:", robotId) while True: # Analyze market conditions and generate trading signals records = exchange.GetRecords("BTC_USDT") if not records or len(records) < 20: Sleep(5000) continue # Simple moving average crossover strategy ma5 = TA.MA(records, 5) ma20 = TA.MA(records, 20) signal = "HOLD" if ma5[-1] > ma20[-1] and ma5[-2] <= ma20[-2]: signal = "BUY" elif ma5[-1] < ma20[-1] and ma5[-2] >= ma20[-2]: signal = "SELL" # Broadcast the trading signal signalData = { "timestamp": time.time() * 1000, "symbol": "BTC_USDT", "signal": signal, "price": records[-1]["Close"], "ma5": ma5[-1], "ma20": ma20[-1] } SetChannelData(signalData) LogStatus("Main Strategy - Signal Broadcast\n" + "Signal: {}\n".format(signal) + "Price: ${:.2f}\n".format(signalData["price"]) + "MA5: {:.2f}\n".format(signalData["ma5"]) + "MA20: {:.2f}".format(signalData["ma20"])) Sleep(60000)rustfn main() { let robotId = _G!(); Log!("Main strategy started, Bot ID:", robotId); loop { // Analyze market conditions and generate trading signals let records = match exchange.GetRecords("BTC_USDT", None, None) { Ok(r) if r.len() >= 20 => r, _ => { Sleep(5000); continue; } }; // Simple moving average crossover strategy let ma5 = TA.MA(&records, 5); let ma20 = TA.MA(&records, 20); let n = ma5.len(); let mut signal = "HOLD"; if ma5[n - 1] > ma20[n - 1] && ma5[n - 2] <= ma20[n - 2] { signal = "BUY"; } else if ma5[n - 1] < ma20[n - 1] && ma5[n - 2] >= ma20[n - 2] { signal = "SELL"; } // Broadcast the trading signal // Rust's SetChannelData only accepts a string argument, so use format! to build the JSON text let price = records[records.len() - 1].Close; let signalData = format!( r#"{{"timestamp": {}, "symbol": "BTC_USDT", "signal": "{}", "price": {}, "ma5": {}, "ma20": {}}}"#, Unix() * 1000, signal, price, ma5[n - 1], ma20[n - 1] ); SetChannelData(&signalData); LogStatus!(format!( "Main Strategy - Signal Broadcast\nSignal: {}\nPrice: ${:.2}\nMA5: {:.2}\nMA20: {:.2}", signal, price, ma5[n - 1], ma20[n - 1] )); Sleep(60000); } }c++ -
Real-World Application Scenarios
Scenario 1: Master-Follower Strategy Coordinated Trading
Follower Strategy (Signal Receiving and Execution End)
javascriptfunction main() { var masterRobotId = "632799" // Live trading ID of the master strategy var lastSignal = null Log("Follower strategy started, subscribing to main strategy:", masterRobotId) while(true) { // Get the signal from the master strategy var signalData = GetChannelData(masterRobotId) if (signalData === null) { LogStatus("Waiting for main strategy signal...") Sleep(5000) continue } // Check whether there is a new signal if (lastSignal !== signalData.signal) { Log("Received new signal:", signalData.signal, "Price:", signalData.price) // Execute the trade if (signalData.signal === "BUY") { var ticker = exchange.GetTicker(signalData.symbol) if (ticker) { exchange.Buy(ticker.Last, 0.01) Log("Executing buy, Price:", ticker.Last) } } else if (signalData.signal === "SELL") { var ticker = exchange.GetTicker(signalData.symbol) if (ticker) { exchange.Sell(ticker.Last, 0.01) Log("Executing sell, Price:", ticker.Last) } } lastSignal = signalData.signal } LogStatus("Follower Strategy - Following Main Strategy\n" + "Current Signal: " + signalData.signal + "\n" + "Signal Price: $" + signalData.price.toFixed(2) + "\n" + "Signal Time: " + _D(signalData.timestamp)) Sleep(5000) } }pythondef main(): masterRobotId = "632799" # Live trading ID of the master strategy lastSignal = None Log("Follower strategy started, subscribing to main strategy:", masterRobotId) while True: # Get the signal from the master strategy signalData = GetChannelData(masterRobotId) if signalData is None: LogStatus("Waiting for main strategy signal...") Sleep(5000) continue # Check whether there is a new signal if lastSignal != signalData["signal"]: Log("Received new signal:", signalData["signal"], "Price:", signalData["price"]) # Execute the trade if signalData["signal"] == "BUY": ticker = exchange.GetTicker(signalData["symbol"]) if ticker: exchange.Buy(ticker["Last"], 0.01) Log("Executing buy, Price:", ticker["Last"]) elif signalData["signal"] == "SELL": ticker = exchange.GetTicker(signalData["symbol"]) if ticker: exchange.Sell(ticker["Last"], 0.01) Log("Executing sell, Price:", ticker["Last"]) lastSignal = signalData["signal"] LogStatus("Follower Strategy - Following Main Strategy\n" + "Current Signal: {}\n".format(signalData["signal"]) + "Signal Price: ${:.2f}\n".format(signalData["price"]) + "Signal Time: {}".format(_D(signalData["timestamp"]))) Sleep(5000)rustfn main() { // Rust's GetChannelData() function does not accept a channel ID parameter, so it cannot subscribe to the master strategy's live trading channel, // it can only read the latest data from the current live trading instance's own channel (this demonstrates equivalent signal-processing logic) let mut lastSignal = String::new(); Log!("Follower strategy started"); loop { // Get the signal from the channel let signalData = GetChannelData(); if signalData.is_null() { LogStatus!("Waiting for signal..."); Sleep(5000); continue; } let signal = signalData["signal"].as_str().unwrap_or("").to_string(); let price = signalData["price"].as_f64().unwrap_or(0.0); let symbol = signalData["symbol"].as_str().unwrap_or("BTC_USDT").to_string(); // Check whether there is a new signal if lastSignal != signal { Log!("Received new signal:", &signal, "Price:", price); // Execute the trade if signal == "BUY" { if let Ok(ticker) = exchange.GetTicker(symbol.as_str()) { let _ = exchange.Buy(ticker.Last, 0.01); Log!("Executing buy, Price:", ticker.Last); } } else if signal == "SELL" { if let Ok(ticker) = exchange.GetTicker(symbol.as_str()) { let _ = exchange.Sell(ticker.Last, 0.01); Log!("Executing sell, Price:", ticker.Last); } } lastSignal = signal.clone(); } LogStatus!(format!( "Follower Strategy\nCurrent Signal: {}\nSignal Price: ${:.2}\nSignal Time: {}", signal, price, _D(signalData["timestamp"].as_i64().unwrap_or(0)) )); Sleep(5000); } }c++ -
Scenario 2: Multi-Strategy Status Monitoring
Monitoring Strategies
javascriptfunction main() { // List of live-trading bot IDs to monitor var monitorList = ["632799", "632800", "632801"] while(true) { var table = { type: "table", title: "Strategy Running Status Monitor", cols: ["Bot ID", "Status", "Last Update", "Symbol", "Current Price", "PnL"], rows: [] } for (var i = 0; i < monitorList.length; i++) { var robotId = monitorList[i] var data = GetChannelData(robotId) if (data !== null) { var updateTime = _D(data.timestamp) var timeDiff = Date.now() - data.timestamp var status = timeDiff < 120000 ? "Running" : "Error" table.rows.push([ robotId, status, updateTime, data.symbol || "-", data.lastPrice ? "$" + data.lastPrice.toFixed(2) : "-", data.profit ? data.profit.toFixed(2) + "%" : "-" ]) } else { table.rows.push([ robotId, "Waiting for Data", "-", "-", "-", "-" ]) } } LogStatus("`" + JSON.stringify(table) + "`") Sleep(10000) } }pythondef main(): # List of live-trading bot IDs to monitor monitorList = ["632799", "632800", "632801"] while True: table = { "type": "table", "title": "Strategy Running Status Monitor", "cols": ["Bot ID", "Status", "Last Update", "Symbol", "Current Price", "PnL"], "rows": [] } for robotId in monitorList: data = GetChannelData(robotId) if data is not None: updateTime = _D(data["timestamp"]) timeDiff = time.time() * 1000 - data["timestamp"] status = "Running" if timeDiff < 120000 else "Error" table["rows"].append([ robotId, status, updateTime, data.get("symbol", "-"), "${:.2f}".format(data["lastPrice"]) if "lastPrice" in data else "-", "{:.2f}%".format(data["profit"]) if "profit" in data else "-" ]) else: table["rows"].append([ robotId, "Waiting for Data", "-", "-", "-", "-" ]) LogStatus("`" + json.dumps(table) + "`") Sleep(10000)rustfn main() { // Rust's GetChannelData() function does not accept a channel ID parameter, so it cannot subscribe to other bots' channels for monitoring; // it can only read the latest data from the current bot's own channel (this demonstrates the equivalent status-table display logic) loop { let data = GetChannelData(); let row = if !data.is_null() { let timestamp = data["timestamp"].as_i64().unwrap_or(0); let updateTime = _D(timestamp); let timeDiff = Unix() * 1000 - timestamp; let status = if timeDiff < 120000 { "Running" } else { "Error" }; format!( r#"["{}", "{}", "{}", "{}"]"#, _G!(), status, updateTime, data["symbol"].as_str().unwrap_or("-") ) } else { format!(r#"["{}", "Waiting for Data", "-", "-"]"#, _G!()) }; // Build the table JSON text (Rust has no JSON serialization, so use format! to concatenate) let table = format!( r#"{{"type": "table", "title": "Strategy Running Status Monitor", "cols": ["Bot ID", "Status", "Last Update", "Symbol"], "rows": [{}]}}"#, row ); LogStatus!(format!("`{}`", table)); Sleep(10000); } }c++ -
API Function Reference
### SetChannelData(data)
Function: Publishes the latest status data to a channel
Parameters:
- data: The data to publish, which can be any JSON-serializable data structure
Return Value: None
Characteristics:
- Non-blocking call
- Overwrites the previous data; does not accumulate historical records
- Automatically uses the current live trading ID as the channel ID
Data Size Limit:
- Must not exceed 1024 bytes after JSON serialization
- It is recommended to transmit only the necessary status information
Detailed Documentation: SetChannelData
### GetChannelData(robotId)
Function: Subscribes to the channel data of a specified live trading bot
Parameters:
- robotId: The live trading ID to subscribe to (string or number)
Return Value:
- Returns null on the first call; a retry is required
- Returns the latest data of the channel upon success
Characteristics:
- Non-blocking call
- Supports subscribing to multiple channels
- Supports subscribing to its own channel
Detailed Documentation: GetChannelData
## Notes
- First call returns null: The
GetChannelData()function returnsnullon its first call. This is normal behavior, as it needs to wait for data synchronization to complete. It is recommended to add a null check in your code.- Data overwrite mechanism: Only the latest status is stored on the channel. Calling
SetChannelData()overwrites the previous data. If you need to preserve historical data, you should record it yourself on the subscriber side.- Non-blocking characteristic: All channel communication functions are non-blocking calls and will not affect the execution of the strategy's main flow. However, this also means the real-time delivery of data cannot be guaranteed.
- Data size limit: The data passed to SetChannelData must not exceed 1024 bytes after JSON serialization. You should transmit only the necessary status information, such as key data like trading signals, prices, and positions, and avoid transmitting complete candlestick (K-line) arrays or large amounts of historical data.
- Live trading environment limitation: The channel communication feature is primarily intended for the live trading environment and may be restricted or unavailable in the backtesting system.
- Obtaining the live trading ID: You can obtain the current live trading ID via the
_G()function, or view it in the platform interface.- Security considerations: Channel data may be subscribed to by other live trading bots with the appropriate permissions, so do not transmit sensitive information (such as API keys) through the channel.
## Best Practices
- Reasonable update frequency: Set the data update frequency according to your actual needs to avoid wasting resources due to overly frequent updates.
- Data structure design: Design a clear data structure and include the necessary metadata (such as timestamps and version numbers) to facilitate processing on the subscriber side.
- Error handling: The subscriber side should handle null return values, and the broadcasting side should ensure the data format is correct.
- Status version control: Include a version number or update ID in the data to help the subscriber side determine whether new data is available.
- Monitoring and alerting: For critical communication links, it is recommended to implement timeout monitoring and alerting mechanisms.
- Testing and validation: Before using it in production, first validate the stability and latency of channel communication in a test environment.
- Documentation: Document the channel data format and communication protocol to facilitate future maintenance and team collaboration.
See Also