Type/to search
Built-in Functions
Global
Log
Log
LogStatus
LogProfit
LogProfitReset
LogReset
LogVacuum
EnableLog
Chart
KLineChart
console.log
console.error
exchange.Log
Market
Trade
Account
Futures
Exchange
IO
Network
Storage
Threads
TA
Talib
OS
Structures
Built-in Variables and Constants
Conventions
Exchanges

Refreshes the status bar of a backtest or live trading page with text, tables, buttons or component objects with a type (KPI cards, charts, tabs and more); each call replaces the previous content.

LogStatus(...msgs)

Examples

  • Supports setting the color of the output content.

    javascript
    function main() { LogStatus('This is a normal status message') LogStatus('This is a red font status message#ff0000') LogStatus('This is a multi-line status message\nI am the second line') }
    python
    def main(): LogStatus('This is a normal status message') LogStatus('This is a red font status message#ff0000') LogStatus('This is a multi-line status message\nI am the second line')
    rust
    fn main() { LogStatus!("This is a normal status message"); LogStatus!("This is a red font status message#ff0000"); LogStatus!("This is a multi-line status message\nI am the second line"); }
  • Pass a component object directly: a line of text followed by a last-price KPI card.

    javascript
    function main() { var ticker = exchange.GetTicker() if (!ticker) return // Pass the component object directly: no JSON.stringify, no backticks LogStatus("Updated " + _D(), {type: "kpi", title: "Last", value: ticker.Last}) // Output: the text "Updated ..." followed by a last-price KPI card }
    python
    def main(): ticker = exchange.GetTicker() if not ticker: return # Pass the component dict directly: no json.dumps, no backticks LogStatus("Updated " + _D(), {"type": "kpi", "title": "Last", "value": ticker["Last"]}) # Output: the text "Updated ..." followed by a last-price KPI card
    rust
    fn main() { let Ok(t) = exchange.GetTicker(None) else { return }; // The component is one-line JSON text after the text, without backticks let kpi = format!(r#"{{"type": "kpi", "title": "Last", "value": {}}}"#, t.Last); LogStatus!(format!("Updated {}", _D(None)), kpi); // Output: the text "Updated ..." followed by a last-price KPI card }
  • A dashboard: a grid of KPI cards, an equity curve and a positions table.

    javascript
    function main() { exchange.SetContractType("swap") var times = [], curve = [], f2 = {precision: 2} while (true) { var account = exchange.GetAccount() var positions = exchange.GetPositions() if (account && positions) { var profit = 0 var rows = positions.map(function (p) { profit += p.Profit var side = p.Type == PD_LONG || p.Type == PD_LONG_YD ? "Long" : "Short" return [p.Symbol, side, p.Amount, p.Price, p.Profit] }) // Futures accounts report Equity; otherwise use available + frozen var equity = account.Equity || account.Balance + account.FrozenBalance times.push(Date.now()) curve.push(equity) if (curve.length > 500) { times.shift(); curve.shift() } var color = profit >= 0 ? "up" : "down" LogStatus({type: "grid", cols: 3, items: [ {type: "kpi", title: "Equity", value: equity, unit: "USDT", format: f2}, {type: "kpi", title: "Available", value: account.Balance, format: f2}, {type: "kpi", title: "Floating PnL", value: profit, color: color}, {type: "chart", span: 3, kind: "area", title: "Equity curve", x: times, series: [{name: "Equity", data: curve}]}, {type: "table", span: 3, title: "Positions", rows: rows, cols: ["Symbol", "Side", "Amount", "Avg price", "Floating PnL"]} ]}) } Sleep(60 * 1000) } }
    python
    import time def main(): exchange.SetContractType("swap") times, curve, f2 = [], [], {"precision": 2} while True: account = exchange.GetAccount() positions = exchange.GetPositions() if account and positions is not None: rows, profit = [], 0 for p in positions: profit += p["Profit"] side = "Long" if p["Type"] in (PD_LONG, PD_LONG_YD) else "Short" rows.append([p["Symbol"], side, p["Amount"], p["Price"], p["Profit"]]) # Futures accounts report Equity; otherwise use available + frozen equity = account.get("Equity") or account["Balance"] + account["FrozenBalance"] times.append(int(time.time() * 1000)) curve.append(equity) del times[:-500], curve[:-500] color = "up" if profit >= 0 else "down" LogStatus({"type": "grid", "cols": 3, "items": [ {"type": "kpi", "title": "Equity", "value": equity, "unit": "USDT", "format": f2}, {"type": "kpi", "title": "Available", "value": account["Balance"], "format": f2}, {"type": "kpi", "title": "Floating PnL", "value": profit, "color": color}, {"type": "chart", "span": 3, "kind": "area", "title": "Equity curve", "x": times, "series": [{"name": "Equity", "data": curve}]}, {"type": "table", "span": 3, "title": "Positions", "rows": rows, "cols": ["Symbol", "Side", "Amount", "Avg price", "Floating PnL"]} ]}) Sleep(60 * 1000)
    rust
    fn join<T: ToString>(v: &[T]) -> String { v.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(",") } fn main() { if exchange.SetContractType("swap").is_err() { return; } let (mut times, mut curve) = (Vec::new(), Vec::new()); loop { if let (Ok(a), Ok(ps)) = (exchange.GetAccount(), exchange.GetPositions(None)) { let profit: f64 = ps.iter().map(|p| p.Profit).sum(); let rows: Vec<String> = ps.iter().map(|p| { let long = p.Type == PD_LONG || p.Type == PD_LONG_YD; let side = if long { "Long" } else { "Short" }; format!(r#"["{}","{}",{},{},{}]"#, p.Symbol, side, p.Amount, p.Price, p.Profit) }).collect(); // Futures accounts report Equity; otherwise use available + frozen let equity = if a.Equity > 0.0 { a.Equity } else { a.Balance + a.FrozenBalance }; times.push(UnixNano() / 1_000_000); curve.push(equity); if curve.len() > 500 { times.remove(0); curve.remove(0); } let color = if profit >= 0.0 { "up" } else { "down" }; LogStatus!(format!(r#"{{"type": "grid", "cols": 3, "items": [ {{"type": "kpi", "title": "Equity", "value": {}, "unit": "USDT", "format": {{"precision": 2}}}}, {{"type": "kpi", "title": "Available", "value": {}, "format": {{"precision": 2}}}}, {{"type": "kpi", "title": "Floating PnL", "value": {}, "color": "{}"}}, {{"type": "chart", "span": 3, "kind": "area", "title": "Equity curve", "x": [{}], "series": [{{"name": "Equity", "data": [{}]}}]}}, {{"type": "table", "span": 3, "title": "Positions", "rows": [{}], "cols": ["Symbol", "Side", "Amount", "Avg price", "Floating PnL"]}}]}}"#, equity, a.Balance, profit, color, join(&times), join(&curve), rows.join(","))); } Sleep(60 * 1000); } }
  • Enhanced table: column formats, up/down colors and units; clicking a header sorts by the raw numbers.

    javascript
    function main() { exchange.SetContractType("swap") var positions = exchange.GetPositions() if (!positions) return var rows = positions.map(function (p) { var ratio = p.Margin > 0 ? p.Profit / p.Margin : 0 // Cell object: unit and color for this cell only var lever = {value: p.MarginLevel, unit: "x", color: p.MarginLevel > 10 ? "warning" : "muted"} return [p.Symbol, p.Amount, p.Price, p.Profit, ratio, lever] }) var f2 = {precision: 2}, pct = {percent: true, precision: 2} // A table on its own: JavaScript and Python add the backticks automatically LogStatus({type: "table", title: "Positions", rows: rows, cols: [ "Symbol", {title: "Amount", align: "right"}, {title: "Avg price", format: f2, align: "right"}, {title: "Floating PnL", format: f2, color: "sign", unit: "USDT", align: "right"}, {title: "Return", format: pct, color: "sign", align: "right"}, {title: "Leverage", align: "right"} ]}) // Output: PnL and return colored by sign, sorted by value rather than text }
    python
    def main(): exchange.SetContractType("swap") positions = exchange.GetPositions() if positions is None: return rows = [] for p in positions: ratio = p["Profit"] / p["Margin"] if p["Margin"] > 0 else 0 # Cell object: unit and color for this cell only lever = {"value": p["MarginLevel"], "unit": "x", "color": "warning" if p["MarginLevel"] > 10 else "muted"} rows.append([p["Symbol"], p["Amount"], p["Price"], p["Profit"], ratio, lever]) f2, pct = {"precision": 2}, {"percent": True, "precision": 2} # A table on its own: JavaScript and Python add the backticks automatically LogStatus({"type": "table", "title": "Positions", "rows": rows, "cols": [ "Symbol", {"title": "Amount", "align": "right"}, {"title": "Avg price", "format": f2, "align": "right"}, {"title": "Floating PnL", "format": f2, "color": "sign", "unit": "USDT", "align": "right"}, {"title": "Return", "format": pct, "color": "sign", "align": "right"}, {"title": "Leverage", "align": "right"} ]}) # Output: PnL and return colored by sign, sorted by value rather than text
    rust
    fn main() { if exchange.SetContractType("swap").is_err() { return; } let Ok(positions) = exchange.GetPositions(None) else { return }; let rows: Vec<String> = positions.iter().map(|p| { let ratio = if p.Margin > 0.0 { p.Profit / p.Margin } else { 0.0 }; let warn = if p.MarginLevel > 10.0 { "warning" } else { "muted" }; // Cell object: unit and color for this cell only let lever = format!(r#"{{"value": {}, "unit": "x", "color": "{}"}}"#, p.MarginLevel, warn); format!(r#"["{}", {}, {}, {}, {}, {}]"#, p.Symbol, p.Amount, p.Price, p.Profit, ratio, lever) }).collect(); let table = format!(r#"{{"type": "table", "title": "Positions", "rows": [{}], "cols": [ "Symbol", {{"title": "Amount", "align": "right"}}, {{"title": "Avg price", "format": {{"precision": 2}}, "align": "right"}}, {{"title": "Floating PnL", "format": {{"precision": 2}}, "color": "sign", "unit": "USDT", "align": "right"}}, {{"title": "Return", "format": {{"percent": true, "precision": 2}}, "color": "sign", "align": "right"}}, {{"title": "Leverage", "align": "right"}}]}}"#, rows.join(",")); // A table on its own needs backticks, and the JSON inside must be one line LogStatus!(format!("`{}`", table.replace('\n', ""))); // Output: PnL and return colored by sign, sorted by value rather than text }
  • Tabs for market data, positions and the account; the selected tab survives refreshes.

    javascript
    function main() { exchange.SetContractType("swap") while (true) { var ticker = exchange.GetTicker() var account = exchange.GetAccount() var positions = exchange.GetPositions() if (ticker && account && positions) { var rows = positions.map(function (p) { return [p.Symbol, p.Amount, p.Profit] }) var table = {type: "table", cols: ["Symbol", "Amount", "Floating PnL"], rows: rows} LogStatus({type: "tabs", id: "main", items: [ {title: "Market", cols: 3, items: [ {type: "kpi", title: "Last", value: ticker.Last}, {type: "kpi", title: "Bid", value: ticker.Buy}, {type: "kpi", title: "Ask", value: ticker.Sell} ]}, {title: "Positions", items: [table]}, {title: "Account", items: [{type: "stat", cols: 2, items: [ {label: "Available", value: account.Balance}, {label: "Frozen", value: account.FrozenBalance} ]}]} ]}) } Sleep(5000) } }
    python
    def main(): exchange.SetContractType("swap") while True: ticker = exchange.GetTicker() account = exchange.GetAccount() positions = exchange.GetPositions() if ticker and account and positions is not None: rows = [[p["Symbol"], p["Amount"], p["Profit"]] for p in positions] table = {"type": "table", "cols": ["Symbol", "Amount", "Floating PnL"], "rows": rows} LogStatus({"type": "tabs", "id": "main", "items": [ {"title": "Market", "cols": 3, "items": [ {"type": "kpi", "title": "Last", "value": ticker["Last"]}, {"type": "kpi", "title": "Bid", "value": ticker["Buy"]}, {"type": "kpi", "title": "Ask", "value": ticker["Sell"]} ]}, {"title": "Positions", "items": [table]}, {"title": "Account", "items": [{"type": "stat", "cols": 2, "items": [ {"label": "Available", "value": account["Balance"]}, {"label": "Frozen", "value": account["FrozenBalance"]} ]}]} ]}) Sleep(5000)
    rust
    fn main() { if exchange.SetContractType("swap").is_err() { return; } loop { let t = exchange.GetTicker(None); let (a, ps) = (exchange.GetAccount(), exchange.GetPositions(None)); if let (Ok(t), Ok(a), Ok(ps)) = (t, a, ps) { let rows: Vec<String> = ps.iter() .map(|p| format!(r#"["{}", {}, {}]"#, p.Symbol, p.Amount, p.Profit)) .collect(); LogStatus!(format!(r#"{{"type": "tabs", "id": "main", "items": [ {{"title": "Market", "cols": 3, "items": [{{"type": "kpi", "title": "Last", "value": {}}}, {{"type": "kpi", "title": "Bid", "value": {}}}, {{"type": "kpi", "title": "Ask", "value": {}}}]}}, {{"title": "Positions", "items": [{{"type": "table", "cols": ["Symbol", "Amount", "Floating PnL"], "rows": [{}]}}]}}, {{"title": "Account", "items": [{{"type": "stat", "cols": 2, "items": [ {{"label": "Available", "value": {}}}, {{"label": "Frozen", "value": {}}}]}}]}}]}}"#, t.Last, t.Buy, t.Sell, rows.join(","), a.Balance, a.FrozenBalance)); } Sleep(5000); } }
  • Several charts: sparkline, gauge, progress, pie, histogram and heatmap.

    javascript
    function main() { var r = exchange.GetRecords(PERIOD_H1) var account = exchange.GetAccount() if (!r || r.length < 73 || !account) return r = r.slice(-73) var closes = r.map(function (b) { return b.Close }) var rets = closes.slice(1).map(function (c, i) { return c / closes[i] - 1 }) var day = r.slice(-24) var hi = Math.max.apply(null, day.map(function (b) { return b.High })) var lo = Math.min.apply(null, day.map(function (b) { return b.Low })) var pos = hi > lo ? (closes[72] - lo) / (hi - lo) : 0.5 var total = account.Balance + account.FrozenBalance var frozen = total > 0 ? account.FrozenBalance / total : 0 var pct = {percent: true, precision: 2} LogStatus({type: "grid", cols: 3, items: [ {type: "sparkline", title: "Last 24 hours", data: closes.slice(-24)}, {type: "gauge", title: "Position in 24h range", value: pos, format: {percent: true}, thresholds: [{value: 0.8, color: "up"}]}, {type: "progress", title: "Frozen share", value: frozen}, {type: "pie", title: "Funds", items: [ {name: "Available", value: account.Balance}, {name: "Frozen", value: account.FrozenBalance}]}, {type: "histogram", title: "Hourly change distribution", data: rets, format: pct}, {type: "heatmap", title: "Hourly change", format: pct, x: day.map(function (b) { return _D(b.Time, "hh:00") }), y: ["2 days ago", "Yesterday", "Today"], z: [rets.slice(0, 24), rets.slice(24, 48), rets.slice(48, 72)]} ]}) }
    python
    def main(): r = exchange.GetRecords(PERIOD_H1) account = exchange.GetAccount() if not r or len(r) < 73 or not account: return r = r[-73:] closes = [b["Close"] for b in r] rets = [closes[i] / closes[i - 1] - 1 for i in range(1, 73)] day = r[-24:] hi, lo = max(b["High"] for b in day), min(b["Low"] for b in day) pos = (closes[72] - lo) / (hi - lo) if hi > lo else 0.5 total = account["Balance"] + account["FrozenBalance"] frozen = account["FrozenBalance"] / total if total > 0 else 0 pct = {"percent": True, "precision": 2} LogStatus({"type": "grid", "cols": 3, "items": [ {"type": "sparkline", "title": "Last 24 hours", "data": closes[-24:]}, {"type": "gauge", "title": "Position in 24h range", "value": pos, "format": {"percent": True}, "thresholds": [{"value": 0.8, "color": "up"}]}, {"type": "progress", "title": "Frozen share", "value": frozen}, {"type": "pie", "title": "Funds", "items": [ {"name": "Available", "value": account["Balance"]}, {"name": "Frozen", "value": account["FrozenBalance"]}]}, {"type": "histogram", "title": "Hourly change distribution", "data": rets, "format": pct}, {"type": "heatmap", "title": "Hourly change", "format": pct, "x": [_D(b["Time"] / 1000, "%H:00") for b in day], "y": ["2 days ago", "Yesterday", "Today"], "z": [rets[0:24], rets[24:48], rets[48:72]]} ]})
    rust
    fn join<T: ToString>(v: &[T]) -> String { v.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(",") } fn main() { let Ok(r) = exchange.GetRecords(None, PERIOD_H1, None) else { return }; let Ok(a) = exchange.GetAccount() else { return }; if r.len() < 73 { return; } let r = &r[r.len() - 73..]; let closes: Vec<f64> = r.iter().map(|b| b.Close).collect(); let rets: Vec<f64> = closes.windows(2).map(|w| w[1] / w[0] - 1.0).collect(); let day = &r[49..]; let hi = day.iter().map(|b| b.High).fold(f64::MIN, f64::max); let lo = day.iter().map(|b| b.Low).fold(f64::MAX, f64::min); let pos = if hi > lo { (closes[72] - lo) / (hi - lo) } else { 0.5 }; let total = a.Balance + a.FrozenBalance; let frozen = if total > 0.0 { a.FrozenBalance / total } else { 0.0 }; // Rust's _D() always prints UTC time let hours: Vec<String> = day.iter() .map(|b| format!("\"{}:00\"", &_D(b.Time)[11..13])) .collect(); let z = format!("[{}],[{}],[{}]", join(&rets[..24]), join(&rets[24..48]), join(&rets[48..])); LogStatus!(format!(r#"{{"type": "grid", "cols": 3, "items": [ {{"type": "sparkline", "title": "Last 24 hours", "data": [{}]}}, {{"type": "gauge", "title": "Position in 24h range", "value": {}, "format": {{"percent": true}}, "thresholds": [{{"value": 0.8, "color": "up"}}]}}, {{"type": "progress", "title": "Frozen share", "value": {}}}, {{"type": "pie", "title": "Funds", "items": [{{"name": "Available", "value": {}}}, {{"name": "Frozen", "value": {}}}]}}, {{"type": "histogram", "title": "Hourly change distribution", "data": [{}], "format": {{"percent": true, "precision": 2}}}}, {{"type": "heatmap", "title": "Hourly change", "format": {{"percent": true, "precision": 2}}, "x": [{}], "y": ["2 days ago", "Yesterday", "Today"], "z": [{}]}}]}}"#, join(&closes[49..]), pos, frozen, a.Balance, a.FrozenBalance, join(&rets), hours.join(","), z)); }
  • A title line with badges, followed by an alert, a Markdown note and a timeline of events.

    javascript
    function main() { var events = [{time: Date.now(), text: "Strategy started", color: "primary"}] while (true) { var account = exchange.GetAccount() if (account) { var low = account.Balance < 100 if (low) { events.unshift({time: Date.now(), text: "Funds below 100", color: "down"}) } events = events.slice(0, 10) var alert = low ? {type: "alert", level: "warning", title: "Low funds", text: "Below 100, opening paused"} : {type: "alert", level: "success", title: "Risk check passed"} // "\n" splits lines: text and badges, then stacked components LogStatus("Grid strategy", {type: "badge", items: [ {text: IsVirtual() ? "Backtest" : "Live", color: "muted"}, {text: low ? "Paused" : "Running", color: low ? "warning" : "up"} ]}, "\n", [ alert, {type: "markdown", text: "#### Notes\n- Spacing **0.5%**\n- 0.01 per grid"}, {type: "timeline", title: "Recent events", items: events} ]) } Sleep(10 * 1000) } }
    python
    import time def main(): now = lambda: int(time.time() * 1000) events = [{"time": now(), "text": "Strategy started", "color": "primary"}] while True: account = exchange.GetAccount() if account: low = account["Balance"] < 100 if low: events.insert(0, {"time": now(), "text": "Funds below 100", "color": "down"}) del events[10:] if low: alert = {"type": "alert", "level": "warning", "title": "Low funds", "text": "Below 100, opening paused"} else: alert = {"type": "alert", "level": "success", "title": "Risk check passed"} # "\n" splits lines: text and badges, then stacked components LogStatus("Grid strategy", {"type": "badge", "items": [ {"text": "Backtest" if IsVirtual() else "Live", "color": "muted"}, {"text": "Paused" if low else "Running", "color": "warning" if low else "up"} ]}, "\n", [ alert, {"type": "markdown", "text": "#### Notes\n- Spacing **0.5%**\n- 0.01 per grid"}, {"type": "timeline", "title": "Recent events", "items": events} ]) Sleep(10 * 1000)
    rust
    fn item(text: &str, color: &str) -> String { format!(r#"{{"text": "{}", "color": "{}"}}"#, text, color) } fn ev(text: &str, color: &str) -> String { let ms = UnixNano() / 1_000_000; format!(r#"{{"time": {}, "text": "{}", "color": "{}"}}"#, ms, text, color) } fn main() { let mut events = vec![ev("Strategy started", "primary")]; loop { if let Ok(a) = exchange.GetAccount() { let low = a.Balance < 100.0; if low { events.insert(0, ev("Funds below 100", "down")); events.truncate(10); } let mode = if IsVirtual() { "Backtest" } else { "Live" }; let (state, color) = if low { ("Paused", "warning") } else { ("Running", "up") }; let badge = format!(r#"{{"type": "badge", "items": [{}, {}]}}"#, item(mode, "muted"), item(state, color)); let (level, title, text) = match low { true => ("warning", "Low funds", "Below 100, opening paused"), false => ("success", "Risk check passed", ""), }; let alert = format!( r#"{{"type": "alert", "level": "{}", "title": "{}", "text": "{}"}}"#, level, title, text ); let notes = r"#### Notes\n- Spacing **0.5%**\n- 0.01 per grid"; let md = format!(r#"{{"type": "markdown", "text": "{}"}}"#, notes); let tl = format!(r#"{{"type": "timeline", "title": "Recent events", "items": [{}]}}"#, events.join(",")); // "\n" splits lines: text and badges, then stacked components (one line each) LogStatus!("Grid strategy", badge, "\n", format!("[{},{},{}]", alert, md, tl)); } Sleep(10 * 1000); } }
  • Mixing with the old style: a backtick table, a button and new components on separate lines.

    javascript
    function main() { var account = exchange.GetAccount() if (!account) return var table = {type: "table", title: "Account", cols: ["Available", "Frozen"], rows: [[account.Balance, account.FrozenBalance]]} var btn = {type: "button", cmd: "pause", name: "Pause"} LogStatus( "`" + JSON.stringify(table) + "`", // Old style: backtick JSON, shown as before "\nSummary", {type: "grid", cols: 3, items: [ // New components go at the end of the line {type: "kpi", title: "Available", value: account.Balance}, table, btn // No backticks inside a grid ]}, "\n`" + JSON.stringify(btn) + "`" ) // Output: the old table, then text with a component grid, then a button read with GetCommand() }
    python
    import json def main(): account = exchange.GetAccount() if not account: return table = {"type": "table", "title": "Account", "cols": ["Available", "Frozen"], "rows": [[account["Balance"], account["FrozenBalance"]]]} btn = {"type": "button", "cmd": "pause", "name": "Pause"} LogStatus( "`" + json.dumps(table) + "`", # Old style: backtick JSON, shown as before "\nSummary", {"type": "grid", "cols": 3, "items": [ # New components go at the end of the line {"type": "kpi", "title": "Available", "value": account["Balance"]}, table, btn # No backticks inside a grid ]}, "\n`" + json.dumps(btn) + "`" ) # Output: the old table, then text with a component grid, then a button read with GetCommand()
    rust
    fn main() { let Ok(a) = exchange.GetAccount() else { return }; // JSON in backticks or after text must be one line, so drop the line breaks let table = format!(r#"{{"type": "table", "title": "Account", "cols": ["Available", "Frozen"], "rows": [[{}, {}]]}}"#, a.Balance, a.FrozenBalance).replace('\n', ""); let btn = r#"{"type": "button", "cmd": "pause", "name": "Pause"}"#; let kpi = format!(r#"{{"type": "kpi", "title": "Available", "value": {}}}"#, a.Balance); // New components go at the end of the line;No backticks inside a grid let grid = format!(r#"{{"type": "grid", "cols": 3, "items": [{}, {}, {}]}}"#, kpi, table, btn); // Old style: backtick JSON, shown as before LogStatus!(format!("`{}`", table), "\nSummary", grid, format!("\n`{}`", btn)); // Output: the old table, then text with a component grid, then a button read with GetCommand() }
  • Example of data output in the status bar.

    javascript
    function main() { var table = {type: 'table', title: 'Position Info', cols: ['Column 1', 'Column 2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]} // After JSON serialization, add ` characters at both ends of the string to have it recognized as a complex message format (tables are currently supported) LogStatus('`' + JSON.stringify(table) + '`') // Table information can also be displayed within multi-line text LogStatus('First line message\n`' + JSON.stringify(table) + '`\nThird line message') // Multiple tables can be displayed simultaneously, and will be grouped and shown as tabs (TAB) LogStatus('`' + JSON.stringify([table, table]) + '`') // Buttons can also be constructed within the table; the strategy receives the content of the cmd property via GetCommand var table = { type: 'table', title: 'Position Operation', cols: ['Column 1', 'Column 2', 'Action'], rows: [ ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'Close All'}] ] } LogStatus('`' + JSON.stringify(table) + '`') // Or construct a standalone button LogStatus('`' + JSON.stringify({'type':'button', 'cmd': 'coverAll', 'name': 'Close All'}) + '`') // Button styles can be customized (bootstrap button attributes) LogStatus('`' + JSON.stringify({'type':'button', 'class': 'btn btn-xs btn-danger', 'cmd': 'coverAll', 'name': 'Close All'}) + '`') }
    python
    import json def main(): table = {"type": "table", "title": "Position Info", "cols": ["Column 1", "Column 2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]} LogStatus('`' + json.dumps(table) + '`') LogStatus('First line message\n`' + json.dumps(table) + '`\nThird line message') LogStatus('`' + json.dumps([table, table]) + '`') table = { "type" : "table", "title" : "Position Operation", "cols" : ["Column 1", "Column 2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "Close All"}] ] } LogStatus('`' + json.dumps(table) + '`') LogStatus('`' + json.dumps({"type": "button", "cmd": "coverAll", "name": "Close All"}) + '`') LogStatus('`' + json.dumps({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "Close All"}) + '`')
    rust
    fn main() { let table = r#"{"type": "table", "title": "Position Info", "cols": ["Column 1", "Column 2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]}"#; // Add ` characters at both ends of the JSON string to have it recognized as a complex message format (tables are currently supported) LogStatus!(format!("`{}`", table)); // Table information can also be displayed within multi-line text LogStatus!(format!("First line message\n`{}`\nThird line message", table)); // Multiple tables can be displayed simultaneously, and will be grouped and shown as tabs (TAB) LogStatus!(format!("`[{},{}]`", table, table)); // Buttons can also be constructed within the table; the strategy receives the content of the cmd property via GetCommand let table = String::from(r#"{"type": "table", "title": "Position Operation", "cols": ["Column 1", "Column 2", "Action"], "rows": ["#) + r#"["abc", "def", {"type": "button", "cmd": "coverAll", "name": "Close All"}]"# + r#"]}"#; LogStatus!(format!("`{}`", table)); // Or construct a standalone button LogStatus!(format!("`{}`", r#"{"type": "button", "cmd": "coverAll", "name": "Close All"}"#)); // Button styles can be customized (bootstrap button attributes) LogStatus!(format!("`{}`", r#"{"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "Close All"}"#)); }
  • Supports designing button controls in the status bar.

    javascript
    function main() { var table = { type: "table", title: "Status Bar Button Styles", cols: ["Default", "Primary", "Success", "Info", "Warning", "Danger"], rows: [ [ {"type":"button", "class": "btn btn-xs btn-default", "name": "Default"}, {"type":"button", "class": "btn btn-xs btn-primary", "name": "Primary"}, {"type":"button", "class": "btn btn-xs btn-success", "name": "Success"}, {"type":"button", "class": "btn btn-xs btn-info", "name": "Info"}, {"type":"button", "class": "btn btn-xs btn-warning", "name": "Warning"}, {"type":"button", "class": "btn btn-xs btn-danger", "name": "Danger"} ] ] } LogStatus("`" + JSON.stringify(table) + "`") }
    python
    import json def main(): table = { "type": "table", "title": "Status Bar Button Styles", "cols": ["Default", "Primary", "Success", "Info", "Warning", "Danger"], "rows": [ [ {"type":"button", "class": "btn btn-xs btn-default", "name": "Default"}, {"type":"button", "class": "btn btn-xs btn-primary", "name": "Primary"}, {"type":"button", "class": "btn btn-xs btn-success", "name": "Success"}, {"type":"button", "class": "btn btn-xs btn-info", "name": "Info"}, {"type":"button", "class": "btn btn-xs btn-warning", "name": "Warning"}, {"type":"button", "class": "btn btn-xs btn-danger", "name": "Danger"} ] ] } LogStatus("`" + json.dumps(table) + "`")
    rust
    fn main() { let table = String::from(r#"{"type": "table", "title": "Status Bar Button Styles", "cols": ["Default", "Primary", "Success", "Info", "Warning", "Danger"], "rows": [["#) + r#"{"type": "button", "class": "btn btn-xs btn-default", "name": "Default"},"# + r#"{"type": "button", "class": "btn btn-xs btn-primary", "name": "Primary"},"# + r#"{"type": "button", "class": "btn btn-xs btn-success", "name": "Success"},"# + r#"{"type": "button", "class": "btn btn-xs btn-info", "name": "Info"},"# + r#"{"type": "button", "class": "btn btn-xs btn-warning", "name": "Warning"},"# + r#"{"type": "button", "class": "btn btn-xs btn-danger", "name": "Danger"}"# + r#"]]}"#; LogStatus!(format!("`{}`", table)); }
  • Configure the disable and description features of status bar buttons.

    javascript
    function main() { var table = { type: "table", title: "Status Bar Button Disable and Description Test", cols: ["Column 1", "Column 2", "Column 3"], rows: [] } var button1 = {"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"} var button2 = {"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true} var button3 = {"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false} table.rows.push([button1, button2, button3]) LogStatus("`" + JSON.stringify(table) + "`") }
    python
    import json def main(): table = { "type": "table", "title": "Status Bar Button Disable and Description Test", "cols": ["Column 1", "Column 2", "Column 3"], "rows": [] } button1 = {"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"} button2 = {"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": True} button3 = {"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": False} table["rows"].append([button1, button2, button3]) LogStatus("`" + json.dumps(table) + "`")
    rust
    fn main() { let button1 = r#"{"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"}"#; let button2 = r#"{"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true}"#; let button3 = r#"{"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false}"#; let table = format!( r#"{{"type": "table", "title": "Status Bar Button Disable and Description Test", "cols": ["Column 1", "Column 2", "Column 3"], "rows": [[{}, {}, {}]]}}"#, button1, button2, button3 ); LogStatus!(format!("`{}`", table)); }
  • In combination with the GetCommand() function, build the interactive functionality of status bar buttons.

    javascript
    function test1() { Log("Calling custom function") } function main() { while (true) { var table = { type: 'table', title: 'Operation', cols: ['Column 1', 'Column 2', 'Action'], rows: [ ['a', '1', { 'type': 'button', 'cmd': "CoverAll", 'name': 'Close All' }], ['b', '1', { 'type': 'button', 'cmd': 10, 'name': 'Send Number' }], ['c', '1', { 'type': 'button', 'cmd': _D(), 'name': 'Call Function' }], ['d', '1', { 'type': 'button', 'cmd': 'test1', 'name': 'Call Custom Function' }] ] } LogStatus(_D(), "\n", '`' + JSON.stringify(table) + '`') var str_cmd = GetCommand() if (str_cmd) { Log("Received interaction data str_cmd:", "Type:", typeof(str_cmd), "Value:", str_cmd) if(str_cmd == "test1") { test1() } } Sleep(500) } }
    python
    import json def test1(): Log("Calling custom function") def main(): while True: table = { "type": "table", "title": "Operation", "cols": ["Column 1", "Column 2", "Action"], "rows": [ ["a", "1", { "type": "button", "cmd": "CoverAll", "name": "Close All" }], ["b", "1", { "type": "button", "cmd": 10, "name": "Send Number" }], ["c", "1", { "type": "button", "cmd": _D(), "name": "Call Function" }], ["d", "1", { "type": "button", "cmd": "test1", "name": "Call Custom Function" }] ] } LogStatus(_D(), "\n", "`" + json.dumps(table) + "`") str_cmd = GetCommand() if str_cmd: Log("Received interaction data str_cmd", "Type:", type(str_cmd), "Value:", str_cmd) if str_cmd == "test1": test1() Sleep(500)
    rust
    fn test1() { Log!("Calling custom function"); } fn main() { loop { let table = String::from(r#"{"type": "table", "title": "Operation", "cols": ["Column 1", "Column 2", "Action"], "rows": ["#) + r#"["a", "1", {"type": "button", "cmd": "CoverAll", "name": "Close All"}],"# + r#"["b", "1", {"type": "button", "cmd": 10, "name": "Send Number"}],"# + &format!(r#"["c", "1", {{"type": "button", "cmd": "{}", "name": "Call Function"}}],"#, _D(None)) + r#"["d", "1", {"type": "button", "cmd": "test1", "name": "Call Custom Function"}]"# + r#"]}"#; LogStatus!(_D(None), "\n", format!("`{}`", table)); if let Some(str_cmd) = GetCommand(-1) { Log!("Received interaction data str_cmd:", "Type:", "String", "Value:", &str_cmd); if str_cmd == "test1" { test1(); } } Sleep(500); } }
  • A button in a status bar table opens a group of input controls (group), where the price control is shown only when a limit order is selected; after GetCommand() receives open:{...} the strategy parses the values of the controls. The fields are described under "Status bar buttons" above.

    javascript
    function main() { var btnOpen = { type: "button", cmd: "open", name: "开仓|Open", group: [{ type: "selected", name: "tradeType", label: "下单类型|Order type", default: 0, settings: {options: ["市价单|Market", "限价单|Limit"]} }, { type: "selected", name: "direction", label: "方向|Direction", default: "buy", settings: {render: "segment", options: [{name: "买入|Buy", value: "buy"}, {name: "卖出|Sell", value: "sell"}]} }, { // shown only when a limit order (tradeType 1) is selected type: "number", name: "price", label: "价格|Price", required: true, settings: {filter: "tradeType==1"} }, { type: "number", name: "amount", label: "数量|Amount", default: 0.01, required: true, settings: {min: 0} }] } var tbl = {type: "table", title: "操作|Actions", cols: ["品种|Symbol", "操作|Action"], rows: [["BTC_USDT", btnOpen]]} while (true) { LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`") var cmd = GetCommand() if (cmd && cmd.indexOf("open:") === 0) { // e.g. open:{"tradeType":1,"direction":"buy","price":60000,"amount":0.01} var msg = JSON.parse(cmd.slice(5)) Log("direction:", msg.direction, "type:", msg.tradeType == 0 ? "market" : "limit", "price:", msg.price, "amount:", msg.amount) } Sleep(1000) } }
    python
    import json def main(): btnOpen = { "type": "button", "cmd": "open", "name": "开仓|Open", "group": [{ "type": "selected", "name": "tradeType", "label": "下单类型|Order type", "default": 0, "settings": {"options": ["市价单|Market", "限价单|Limit"]} }, { "type": "selected", "name": "direction", "label": "方向|Direction", "default": "buy", "settings": {"render": "segment", "options": [{"name": "买入|Buy", "value": "buy"}, {"name": "卖出|Sell", "value": "sell"}]} }, { # shown only when a limit order (tradeType 1) is selected "type": "number", "name": "price", "label": "价格|Price", "required": True, "settings": {"filter": "tradeType==1"} }, { "type": "number", "name": "amount", "label": "数量|Amount", "default": 0.01, "required": True, "settings": {"min": 0} }] } tbl = {"type": "table", "title": "操作|Actions", "cols": ["品种|Symbol", "操作|Action"], "rows": [["BTC_USDT", btnOpen]]} while True: LogStatus(_D(), "\n", "`" + json.dumps(tbl) + "`") cmd = GetCommand() if cmd and cmd.startswith("open:"): # e.g. open:{"tradeType":1,"direction":"buy","price":60000,"amount":0.01} msg = json.loads(cmd[5:]) Log("direction:", msg["direction"], "type:", "market" if msg["tradeType"] == 0 else "limit", "price:", msg.get("price"), "amount:", msg["amount"]) Sleep(1000)
    rust
    fn main() { // the price control is shown only when a limit order (tradeType 1) is selected let btn_open = String::from(r#"{"type": "button", "cmd": "open", "name": "开仓|Open", "group": ["#) + r#"{"type": "selected", "name": "tradeType", "label": "下单类型|Order type", "default": 0, "settings": {"options": ["市价单|Market", "限价单|Limit"]}},"# + r#"{"type": "selected", "name": "direction", "label": "方向|Direction", "default": "buy", "settings": {"render": "segment", "options": [{"name": "买入|Buy", "value": "buy"}, {"name": "卖出|Sell", "value": "sell"}]}},"# + r#"{"type": "number", "name": "price", "label": "价格|Price", "required": true, "settings": {"filter": "tradeType==1"}},"# + r#"{"type": "number", "name": "amount", "label": "数量|Amount", "default": 0.01, "required": true, "settings": {"min": 0}}"# + r#"]}"#; let tbl = format!(r#"{{"type": "table", "title": "操作|Actions", "cols": ["品种|Symbol", "操作|Action"], "rows": [["BTC_USDT", {}]]}}"#, btn_open); loop { LogStatus!(_D(None), "\n", format!("`{}`", tbl)); if let Some(cmd) = GetCommand(-1) { // e.g. open:{"tradeType":1,"direction":"buy","price":60000,"amount":0.01} if cmd.starts_with("open:") { if let Some(msg) = JSONParse(&cmd[5..]) { Log!("direction:", msg["direction"].as_str().unwrap_or(""), "type:", if msg["tradeType"].as_i64().unwrap_or(0) == 0 { "market" } else { "limit" }, "price:", msg["price"].as_f64().unwrap_or(0.0), "amount:", msg["amount"].as_f64().unwrap_or(0.0)); } } } Sleep(1000); } }

Arguments

NameTypeRequiredDescription

msg

string / number / bool / object / array

No

Content to show: text, numbers, table or button objects, Status Bar Components objects, or arrays of them. Several can be passed; they are joined with spaces, and "\n" starts a new line.

See Also

Remarks

During live trading, the information output by the LogStatus() function is not written to the log records; it only updates the content of the current live trading status bar. The live trading database keeps only the latest status bar content, and each call overwrites the previous one.

Input forms

The arguments are turned into text and joined with spaces: strings as is, numbers as text, objects and arrays as JSON (in JavaScript and Python a table object whose type is table is also wrapped in backticks). The result is split into lines at \n; the table shows how each line is displayed.

FormDisplay
Plain textText; a trailing #ff0000 sets the color, the [trans] tag follows the page language
Component object with a type, or an array containing componentsStatus Bar Components; components in an array are stacked top to bottom
Backtick-wrapped JSON: `{...}`Tables, arrays of tables (shown as tabs) and buttons, or components
The whole status bar is one component JSON (it may span several lines)Recognized as a whole
  • At most one JSON block per line. A component without backticks must come last on its line, optionally after some text (for example LogStatus("Updated", kpi)); to show several components on one line put them in an array or a grid, or split lines with "\n".
  • Backtick-wrapped JSON may have text before and after it. Once a line contains backticks, components without backticks on that line are no longer recognized.
  • Without backticks only components are recognized: objects whose type is not a component (such as the result of GetAccount() or a lone button object) and arrays of tables only are shown as JSON text, as before.

Status bar components

Component objects are rendered by type: grid and tabs handle layout; kpi, stat, progress, gauge, sparkline, chart, heatmap, calendar, histogram, pie, timeline, markdown, alert and badge display data; table and button objects can also go into the items of grid and tabs. For the fields and examples of each component see Status Bar Components.

  • grid drops columns with the container width, so it also works on narrow screens.
  • A malformed component (missing required field, unknown type, data over a limit) shows an error card only in its own place, naming the faulty field and showing its JSON; the other components render as usual.
  • Colors follow the page's light or dark theme; up and down follow the page's up/down color setting (FMZ defaults to green for up and red for down).
  • Fully compatible with the old style: text, backtick tables and buttons look the same and can be mixed with components. Tables also support column formats, up/down colors and units; see LogStatus-table.
  • Components are for display only; use the status bar buttons below for interaction. Buttons can go into table cells or into grid and tabs.
  • Limits: 5000 points per chart series, 40000 heatmap cells, 6 nesting levels of grid and tabs; a component over a limit shows an error card.

Status bar buttons

Output a button object as JSON.stringify() wrapped in ` to the status bar, or put it in a status bar table cell (an array of buttons for several buttons in one cell). Each status bar line can contain only one `-wrapped JSON block, so output several buttons on separate lines or in a table. After a click the strategy reads the command with GetCommand; when the live trading is not running a click only shows an error.

Button fields:

FieldDescription
typeAlways button
nameText on the button
cmdCommand sent to the strategy on click; a click does nothing when it is empty
descriptionHint shown on mouse hover
disabledtrue disables the button
classStyle: containing btn-primary, btn-success, btn-info, btn-warning or btn-danger gives that color; btn-sm / btn-lg give medium / large size, small by default
inputOpens one input control on click; fields under "Input controls" below
groupOpens a group of input controls on click; an array whose items have the fields under "Input controls" below

Command received by GetCommand():

ButtonCommandExample
No input / groupcmd as is (a confirmation dialog is shown first)open
With inputcmd:value, strings unquoted, multi-select as an arrayopen:1, open:abc, open:[0,2]
With groupcmd:JSON keyed by each control's name; controls left empty are omittedopen:{"tradeType":0,"price":100}

Input control fields (input or each item of group):

FieldDescription
typenumber, string, selected (drop-down) or boolean (switch)
nameIn group, the key of this control in the command JSON; in input, used as the title when there is no label
labelControl title; description is used when not set
descriptionControl description
defaultDefault value, also accepted as defValue; for a drop-down, the option index or value
requiredtrue makes the control mandatory
settingsControl configuration, see below

settings fields:

FieldDescription
optionsDrop-down options: ["A", "B"] (the command carries the option index) or [{"name": "A", "value": "a"}] (the command carries value)
multipleMulti-select drop-down; the command value is an array
customizableThe drop-down accepts new options typed by the user; selecting one puts the typed text in the command
filterVisibility expression referring to the name of other controls in the group, for example "tradeType==1"; the control is hidden when it is false
disabledDisables the control
min, maxMinimum and maximum of a number
stepStep of a number shown as a slider
unitUnit text shown after the input
renderAppearance. number: slider, date (date-time picker, value in millisecond timestamp); string: textarea (multi-line), date (value yyyy-MM-dd hh:mm:ss), color (value #FF00FF); selected: segment; boolean is always a switch

The older form without settings still works: a drop-down sets its options with defValue: "A|B|C" (the command carries the index), and a visibility condition is written into name as "price@tradeType==1".

Button text, control titles and descriptions, drop-down options, table titles and column names accept bilingual text "中文|English", shown according to the page language; for bilingual text in plain status bar text and table cells use [trans]中文|English[/trans].

Buttons can also go into the items of grid or tabs next to status bar components, without backticks.

The LogStatus() function supports printing base64-encoded images. The image string starts with ` and ends with `. For example: LogStatus("`data:image/png;base64,AAAA`").

When a strategy is running live, if you scroll through the historical records on the live trading page, the status bar enters a dormant state and stops updating; the status bar data is only refreshed when the log is on the first page. The status bar supports outputting base64-encoded images, and also supports outputting base64-encoded images within tables displayed in the status bar. Since encoded image string data is usually very long, no example code is shown here.

Rust has no object literals: write a component as JSON text (with format!, write { and } as {{ and }}), or pass a JsonValue from JSONParse(); the frontend recognizes both. String arguments are never wrapped in backticks, so a table on its own is written as format!("`{}`", table); inside grid, tabs or an array with components no backticks are needed. Numbers must be finite: NaN makes the whole JSON unparsable and it is shown as text.

The LogStatus() function supports directly passing in a Python matplotlib.pyplot object. As long as the object contains the savefig method, it can be passed as a parameter to the LogStatus() function. For example:

python
import matplotlib.pyplot as plt def main(): plt.plot([3,6,2,4,7,1]) LogStatus(plt)