Type/to search
Welcome to FMZ Quant Trading Platform
Programming Languages
JavaScript
TypeScript
Python
Rust
C++
MyLanguage
PINE Language
Blockly Visual Programming
Workflow
Key Security
Live Trading
Strategy Library
Docker
Deploy Docker
One-Click Docker Rental
Manual Deployment of Bot
Docker Operation Precautions
Global IP Address Specification
Command Line Parameters for Bot Program
Live Trading Data Migration
Docker Monitor
Exchange
Strategy Editor
Backtesting System
Strategy Entry Functions
Strategy Framework and API Functions
Template Library
Strategy Parameters
Interactive Controls
Options Trading
Rust Strategy Development Guide
C++ Strategy Writing Guide
JavaScript Strategy Writing Guide
Web3
Built-in Libraries
Extended API Interface
MCP Service
Trading Terminal
Data Explorer
Alpha Factor Analysis Tool
General Protocol
Debugging Tool
Remote Editing
Import and Export of Complete Strategies
Multi-language Support
Live Trading and Strategy Grouping
Live Trading Display
Strategy Sharing and Renting
Live Trading Message Push
Common Causes of Live Trading Errors and Abnormal Exits
Exchange-Specific Notes

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

    javascript
    function 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 } }
    python
    def 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 minute
    rust
    fn 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

    javascript
    function 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 } }
    python
    def 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 seconds
    rust
    fn 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)

    javascript
    function 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) } }
    python
    def 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)
    rust
    fn 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)

    javascript
    function 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) } }
    python
    def 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)
    rust
    fn 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

    javascript
    function 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) } }
    python
    def 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)
    rust
    fn 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 returns null on 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