Communication Between Live Trading Strategies
Every live trading has a channel whose ID is the live trading ID. A live trading publishes data on its own channel with SetChannelData(), and other live tradings read it with GetChannelData(liveTradingId). The data is relayed by the platform server, so it can travel across dockers and servers.
A channel holds the latest state, not a message queue: each publish overwrites the previous data, and a subscriber always reads the current latest copy. If history is needed, the subscriber keeps it itself.
Typical uses:
- Master/follower: a master strategy analyzes the market and publishes signals; several follower strategies read them and trade on their own accounts.
- Status monitoring: each strategy publishes its running status; a monitoring live trading collects them for display or alerts.
- Data sharing: one live trading computes indicators and publishes the results; others use them directly instead of computing them again.
Key points
- The first read subscribes: the first
GetChannelData()call for a channel subscribes to it and returns an empty value (null/None). From then on the server pushes the channel's updates to this live trading, and later calls return the latest data. A subscriber should start reading at startup and handle empty values. - Subscription limit: a live trading can subscribe to at most 10 different channels (UUID channels below included). Beyond that, the call returns an empty value and logs the error
channel subscriber exceed limit. - **Data format**: in JavaScript and Python,
SetChannelData()accepts any JSON-serializable data and the subscriber reads the parsed object. Unchanged data is not sent again. For the data size limit seeSetChannelData. - Rust:
SetChannelData(string)only accepts a string, so build the JSON text yourself;GetChannelData()takes no channel argument and cannot choose the channel to read, so it cannot subscribe to other live tradings or UUID channels. Rust strategies are suited to publishing; write subscribers in JavaScript or Python. - **Cross-platform push**: an external system (a TradingView alert, your own program, etc.) can push data to a given live trading on a channel identified by a 32-character UUID through the extended API's
method=pub; the live trading reads it withGetChannelData(UUID). SeeSetChannelDataandGetChannelDatafor details. - Live trading feature: channels are meant for communication between live tradings; do not rely on them in backtests. The current live trading ID is available from
_G(). - Do not pass keys or other sensitive information through channels.
Basic usage
Examples
-
Publisher: publish a market summary
javascriptfunction main() { var robotId = _G() // current live trading ID, which is also this live trading's channel ID var updateId = 0 while (true) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker) { // publish the latest state, overwriting the previous data SetChannelData({ robotId: robotId, updateId: ++updateId, timestamp: Date.now(), symbol: "BTC_USDT", lastPrice: ticker.Last }) LogStatus("channel", robotId, "publish #", updateId, "last price:", ticker.Last) } Sleep(60000) // publish once a minute } }pythonimport time def main(): robotId = _G() # current live trading ID, which is also this live trading's channel ID updateId = 0 while True: ticker = exchange.GetTicker("BTC_USDT") if ticker: updateId += 1 # publish the latest state, overwriting the previous data SetChannelData({ "robotId": robotId, "updateId": updateId, "timestamp": int(time.time() * 1000), "symbol": "BTC_USDT", "lastPrice": ticker["Last"] }) LogStatus("channel", robotId, "publish #", updateId, "last price:", ticker["Last"]) Sleep(60000) # publish once a minuterustfn main() { let robotId = _G!(); // current live trading ID, which is also this live trading's channel ID let mut updateId = 0; loop { if let Ok(ticker) = exchange.GetTicker("BTC_USDT") { updateId += 1; // Rust's SetChannelData only accepts a string; build the JSON text yourself let state = format!( r#"{{"robotId": "{}", "updateId": {}, "timestamp": {}, "symbol": "BTC_USDT", "lastPrice": {}}}"#, robotId, updateId, Unix() * 1000, ticker.Last ); SetChannelData(&state); LogStatus!("channel", robotId, "publish #", updateId, "last price:", ticker.Last); } Sleep(60000); // publish once a minute } } -
Subscriber: read two channels
javascriptfunction main() { // live trading IDs to subscribe to (change as needed) var channels = ["632799", "632800"] while (true) { var msg = "" for (var i = 0; i < channels.length; i++) { // the first call subscribes and returns null; later calls return the latest data var state = GetChannelData(channels[i]) if (state) { msg += "channel " + channels[i] + ": #" + state.updateId + " " + _D(state.timestamp) + " last " + state.lastPrice + "\n" } else { msg += "channel " + channels[i] + ": waiting for data\n" } } LogStatus(msg) Sleep(5000) } }pythondef main(): # live trading IDs to subscribe to (change as needed) channels = ["632799", "632800"] while True: msg = "" for ch in channels: # the first call subscribes and returns None; later calls return the latest data state = GetChannelData(ch) if state: msg += "channel {}: #{} {} last {}\n".format(ch, state["updateId"], _D(state["timestamp"]), state["lastPrice"]) else: msg += "channel {}: waiting for data\n".format(ch) LogStatus(msg) Sleep(5000)rust// Rust's GetChannelData() takes no channel argument and cannot subscribe to other live tradings' channels -
Scenario: master/follower strategies
The master strategy computes a moving-average crossover signal and publishes it; the follower reads the signal and places an order when it changes.
Master strategy (publishes signals)
javascriptfunction main() { while (true) { var records = exchange.GetRecords("BTC_USDT") if (records && records.length >= 21) { var ma5 = TA.MA(records, 5) var ma20 = TA.MA(records, 20) var n = records.length var 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" } SetChannelData({ timestamp: Date.now(), symbol: "BTC_USDT", signal: signal, price: records[n - 1].Close }) LogStatus("current signal:", signal, "price:", records[n - 1].Close) } Sleep(60000) } }pythonimport time def main(): while True: records = exchange.GetRecords("BTC_USDT") if records and len(records) >= 21: 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" SetChannelData({ "timestamp": int(time.time() * 1000), "symbol": "BTC_USDT", "signal": signal, "price": records[-1]["Close"] }) LogStatus("current signal:", signal, "price:", records[-1]["Close"]) Sleep(60000)rustfn main() { loop { if let Ok(records) = exchange.GetRecords("BTC_USDT", None, None) { let n = records.len(); if n >= 21 { let ma5 = TA.MA(&records, 5); let ma20 = TA.MA(&records, 20); 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"; } let price = records[n - 1].Close; // Rust's SetChannelData only accepts a string; build the JSON text yourself let data = format!( r#"{{"timestamp": {}, "symbol": "BTC_USDT", "signal": "{}", "price": {}}}"#, Unix() * 1000, signal, price ); SetChannelData(&data); LogStatus!("current signal:", signal, "price:", price); } } Sleep(60000); } } -
Follower strategy (reads and executes signals)
javascriptfunction main() { var masterId = "632799" // live trading ID of the master strategy var lastSignal = null while (true) { var data = GetChannelData(masterId) if (!data) { LogStatus("waiting for the master strategy's signal...") } else { if (data.signal !== lastSignal) { Log("new signal:", data.signal, "signal price:", data.price) var ticker = exchange.GetTicker(data.symbol) if (ticker && data.signal === "BUY") { exchange.CreateOrder(data.symbol, "buy", ticker.Last, 0.01) } else if (ticker && data.signal === "SELL") { exchange.CreateOrder(data.symbol, "sell", ticker.Last, 0.01) } lastSignal = data.signal } LogStatus("current signal:", data.signal, "signal time:", _D(data.timestamp)) } Sleep(5000) } }pythondef main(): masterId = "632799" # live trading ID of the master strategy lastSignal = None while True: data = GetChannelData(masterId) if not data: LogStatus("waiting for the master strategy's signal...") else: if data["signal"] != lastSignal: Log("new signal:", data["signal"], "signal price:", data["price"]) ticker = exchange.GetTicker(data["symbol"]) if ticker and data["signal"] == "BUY": exchange.CreateOrder(data["symbol"], "buy", ticker["Last"], 0.01) elif ticker and data["signal"] == "SELL": exchange.CreateOrder(data["symbol"], "sell", ticker["Last"], 0.01) lastSignal = data["signal"] LogStatus("current signal:", data["signal"], "signal time:", _D(data["timestamp"])) Sleep(5000)rust// Rust's GetChannelData() takes no channel argument and cannot read the master strategy's channel -
Scenario: monitoring several strategies
Each strategy publishes its status as the publisher above does; the monitoring live trading reads every channel, shows them in a table and marks those without an update for over 2 minutes as abnormal.
javascriptfunction main() { var monitorList = ["632799", "632800", "632801"] // at most 10 while (true) { var table = {type: "table", title: "Strategy status", cols: ["Live trading ID", "Status", "Last update", "Symbol", "Last price"], rows: []} for (var i = 0; i < monitorList.length; i++) { var data = GetChannelData(monitorList[i]) if (data) { var status = Date.now() - data.timestamp < 120000 ? "running" : "abnormal" table.rows.push([monitorList[i], status, _D(data.timestamp), data.symbol || "-", data.lastPrice || "-"]) } else { table.rows.push([monitorList[i], "waiting for data", "-", "-", "-"]) } } LogStatus("`" + JSON.stringify(table) + "`") Sleep(10000) } }pythonimport json import time def main(): monitorList = ["632799", "632800", "632801"] # at most 10 while True: table = {"type": "table", "title": "Strategy status", "cols": ["Live trading ID", "Status", "Last update", "Symbol", "Last price"], "rows": []} for robotId in monitorList: data = GetChannelData(robotId) if data: status = "running" if time.time() * 1000 - data["timestamp"] < 120000 else "abnormal" table["rows"].append([robotId, status, _D(data["timestamp"]), data.get("symbol", "-"), data.get("lastPrice", "-")]) else: table["rows"].append([robotId, "waiting for data", "-", "-", "-"]) LogStatus("`" + json.dumps(table) + "`") Sleep(10000)rust// Rust's GetChannelData() takes no channel argument and cannot read other live tradings' channels
See Also