输入/搜索内容
内置函数
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
结构体
内置变量与常量
通用约定
交易所

刷新回测或实盘页面的状态栏:输出文字、表格、按钮,或带 type 的组件对象(指标卡、图表、标签页等);每次调用替换上一次的内容。

LogStatus(...msgs)

示例

  • 支持设置输出内容的颜色。

    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"); }
  • 直接传组件对象:文字后面跟一张最新价指标卡。

    javascript
    function main() { var ticker = exchange.GetTicker() if (!ticker) return // 组件对象直接传,不用 JSON.stringify,也不用加反引号 LogStatus("更新时间 " + _D(), {type: "kpi", title: "最新价", value: ticker.Last}) // 输出:一行「更新时间 …」文字,后面是最新价指标卡 }
    python
    def main(): ticker = exchange.GetTicker() if not ticker: return # 组件字典直接传,不用 json.dumps,也不用加反引号 LogStatus("更新时间 " + _D(), {"type": "kpi", "title": "最新价", "value": ticker["Last"]}) # 输出:一行「更新时间 …」文字,后面是最新价指标卡
    rust
    fn main() { let Ok(t) = exchange.GetTicker(None) else { return }; // 组件写成一行 JSON 文本跟在文字后面,不用加反引号 let kpi = format!(r#"{{"type": "kpi", "title": "最新价", "value": {}}}"#, t.Last); LogStatus!(format!("更新时间 {}", _D(None)), kpi); // 输出:一行「更新时间 …」文字,后面是最新价指标卡 }
  • 组合仪表盘:grid排列指标卡、权益曲线和持仓表。

    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 ? "多" : "空" return [p.Symbol, side, p.Amount, p.Price, p.Profit] }) // 期货账户有 Equity(权益),没有时用可用 + 冻结 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: "权益", value: equity, unit: "USDT", format: f2}, {type: "kpi", title: "可用", value: account.Balance, format: f2}, {type: "kpi", title: "浮动盈亏", value: profit, color: color}, {type: "chart", span: 3, kind: "area", title: "权益曲线", x: times, series: [{name: "权益", data: curve}]}, {type: "table", span: 3, title: "持仓", rows: rows, cols: ["品种", "方向", "数量", "均价", "浮动盈亏"]} ]}) } 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 = "多" if p["Type"] in (PD_LONG, PD_LONG_YD) else "空" rows.append([p["Symbol"], side, p["Amount"], p["Price"], p["Profit"]]) # 期货账户有 Equity(权益),没有时用可用 + 冻结 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": "权益", "value": equity, "unit": "USDT", "format": f2}, {"type": "kpi", "title": "可用", "value": account["Balance"], "format": f2}, {"type": "kpi", "title": "浮动盈亏", "value": profit, "color": color}, {"type": "chart", "span": 3, "kind": "area", "title": "权益曲线", "x": times, "series": [{"name": "权益", "data": curve}]}, {"type": "table", "span": 3, "title": "持仓", "rows": rows, "cols": ["品种", "方向", "数量", "均价", "浮动盈亏"]} ]}) 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 { "多" } else { "空" }; format!(r#"["{}","{}",{},{},{}]"#, p.Symbol, side, p.Amount, p.Price, p.Profit) }).collect(); // 期货账户有 Equity(权益),没有时用可用 + 冻结 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": "权益", "value": {}, "unit": "USDT", "format": {{"precision": 2}}}}, {{"type": "kpi", "title": "可用", "value": {}, "format": {{"precision": 2}}}}, {{"type": "kpi", "title": "浮动盈亏", "value": {}, "color": "{}"}}, {{"type": "chart", "span": 3, "kind": "area", "title": "权益曲线", "x": [{}], "series": [{{"name": "权益", "data": [{}]}}]}}, {{"type": "table", "span": 3, "title": "持仓", "rows": [{}], "cols": ["品种", "方向", "数量", "均价", "浮动盈亏"]}}]}}"#, equity, a.Balance, profit, color, join(&times), join(&curve), rows.join(","))); } Sleep(60 * 1000); } }
  • 表格增强:列设置格式、涨跌色和单位,点击表头按原始数值排序。

    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 // 单元格对象:单独指定这个格子的单位和颜色 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} // 单独输出表格:JavaScript、Python 自动加反引号 LogStatus({type: "table", title: "持仓", rows: rows, cols: [ "品种", {title: "数量", align: "right"}, {title: "均价", format: f2, align: "right"}, {title: "浮动盈亏", format: f2, color: "sign", unit: "USDT", align: "right"}, {title: "收益率", format: pct, color: "sign", align: "right"}, {title: "杠杆", align: "right"} ]}) // 输出:盈亏、收益率按正负着色,排序按数值而不是文字 }
    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 # 单元格对象:单独指定这个格子的单位和颜色 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} # 单独输出表格:JavaScript、Python 自动加反引号 LogStatus({"type": "table", "title": "持仓", "rows": rows, "cols": [ "品种", {"title": "数量", "align": "right"}, {"title": "均价", "format": f2, "align": "right"}, {"title": "浮动盈亏", "format": f2, "color": "sign", "unit": "USDT", "align": "right"}, {"title": "收益率", "format": pct, "color": "sign", "align": "right"}, {"title": "杠杆", "align": "right"} ]}) # 输出:盈亏、收益率按正负着色,排序按数值而不是文字
    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" }; // 单元格对象:单独指定这个格子的单位和颜色 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": "持仓", "rows": [{}], "cols": [ "品种", {{"title": "数量", "align": "right"}}, {{"title": "均价", "format": {{"precision": 2}}, "align": "right"}}, {{"title": "浮动盈亏", "format": {{"precision": 2}}, "color": "sign", "unit": "USDT", "align": "right"}}, {{"title": "收益率", "format": {{"percent": true, "precision": 2}}, "color": "sign", "align": "right"}}, {{"title": "杠杆", "align": "right"}}]}}"#, rows.join(",")); // 单独输出表格要自己加反引号,且反引号里的 JSON 要在一行里 LogStatus!(format!("`{}`", table.replace('\n', ""))); // 输出:盈亏、收益率按正负着色,排序按数值而不是文字 }
  • 用tabs分页显示行情、持仓和账户,刷新时保持当前选中的页。

    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: ["品种", "数量", "浮动盈亏"], rows: rows} LogStatus({type: "tabs", id: "main", items: [ {title: "行情", cols: 3, items: [ {type: "kpi", title: "最新价", value: ticker.Last}, {type: "kpi", title: "买一", value: ticker.Buy}, {type: "kpi", title: "卖一", value: ticker.Sell} ]}, {title: "持仓", items: [table]}, {title: "账户", items: [{type: "stat", cols: 2, items: [ {label: "可用", value: account.Balance}, {label: "冻结", 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": ["品种", "数量", "浮动盈亏"], "rows": rows} LogStatus({"type": "tabs", "id": "main", "items": [ {"title": "行情", "cols": 3, "items": [ {"type": "kpi", "title": "最新价", "value": ticker["Last"]}, {"type": "kpi", "title": "买一", "value": ticker["Buy"]}, {"type": "kpi", "title": "卖一", "value": ticker["Sell"]} ]}, {"title": "持仓", "items": [table]}, {"title": "账户", "items": [{"type": "stat", "cols": 2, "items": [ {"label": "可用", "value": account["Balance"]}, {"label": "冻结", "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": "行情", "cols": 3, "items": [{{"type": "kpi", "title": "最新价", "value": {}}}, {{"type": "kpi", "title": "买一", "value": {}}}, {{"type": "kpi", "title": "卖一", "value": {}}}]}}, {{"title": "持仓", "items": [{{"type": "table", "cols": ["品种", "数量", "浮动盈亏"], "rows": [{}]}}]}}, {{"title": "账户", "items": [{{"type": "stat", "cols": 2, "items": [ {{"label": "可用", "value": {}}}, {{"label": "冻结", "value": {}}}]}}]}}]}}"#, t.Last, t.Buy, t.Sell, rows.join(","), a.Balance, a.FrozenBalance)); } Sleep(5000); } }
  • 多种图表:sparkline、gauge、progress、pie、histogram和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: "最近24小时", data: closes.slice(-24)}, {type: "gauge", title: "24小时区间位置", value: pos, format: {percent: true}, thresholds: [{value: 0.8, color: "up"}]}, {type: "progress", title: "冻结资金占比", value: frozen}, {type: "pie", title: "资金分布", items: [ {name: "可用", value: account.Balance}, {name: "冻结", value: account.FrozenBalance}]}, {type: "histogram", title: "小时涨跌分布", data: rets, format: pct}, {type: "heatmap", title: "逐小时涨跌", format: pct, x: day.map(function (b) { return _D(b.Time, "hh:00") }), y: ["前天", "昨天", "今天"], 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": "最近24小时", "data": closes[-24:]}, {"type": "gauge", "title": "24小时区间位置", "value": pos, "format": {"percent": True}, "thresholds": [{"value": 0.8, "color": "up"}]}, {"type": "progress", "title": "冻结资金占比", "value": frozen}, {"type": "pie", "title": "资金分布", "items": [ {"name": "可用", "value": account["Balance"]}, {"name": "冻结", "value": account["FrozenBalance"]}]}, {"type": "histogram", "title": "小时涨跌分布", "data": rets, "format": pct}, {"type": "heatmap", "title": "逐小时涨跌", "format": pct, "x": [_D(b["Time"] / 1000, "%H:00") for b in day], "y": ["前天", "昨天", "今天"], "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 的 _D() 固定输出 UTC 时间 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": "最近24小时", "data": [{}]}}, {{"type": "gauge", "title": "24小时区间位置", "value": {}, "format": {{"percent": true}}, "thresholds": [{{"value": 0.8, "color": "up"}}]}}, {{"type": "progress", "title": "冻结资金占比", "value": {}}}, {{"type": "pie", "title": "资金分布", "items": [{{"name": "可用", "value": {}}}, {{"name": "冻结", "value": {}}}]}}, {{"type": "histogram", "title": "小时涨跌分布", "data": [{}], "format": {{"percent": true, "precision": 2}}}}, {{"type": "heatmap", "title": "逐小时涨跌", "format": {{"percent": true, "precision": 2}}, "x": [{}], "y": ["前天", "昨天", "今天"], "z": [{}]}}]}}"#, join(&closes[49..]), pos, frozen, a.Balance, a.FrozenBalance, join(&rets), hours.join(","), z)); }
  • 标题行加badge状态标签,下面是alert风控提示、markdown说明和timeline事件。

    javascript
    function main() { var events = [{time: Date.now(), text: "策略启动", color: "primary"}] while (true) { var account = exchange.GetAccount() if (account) { var low = account.Balance < 100 if (low) { events.unshift({time: Date.now(), text: "可用资金低于100", color: "down"}) } events = events.slice(0, 10) var alert = low ? {type: "alert", level: "warning", title: "可用资金不足", text: "低于100,暂停开新仓"} : {type: "alert", level: "success", title: "风控检查通过"} // 用 "\n" 分行:第一行文字加标签,第二行是组件数组(从上到下排列) LogStatus("网格策略", {type: "badge", items: [ {text: IsVirtual() ? "回测" : "实盘", color: "muted"}, {text: low ? "暂停开仓" : "运行中", color: low ? "warning" : "up"} ]}, "\n", [ alert, {type: "markdown", text: "#### 策略说明\n- 网格间距 **0.5%**\n- 每格数量 0.01"}, {type: "timeline", title: "最近事件", items: events} ]) } Sleep(10 * 1000) } }
    python
    import time def main(): now = lambda: int(time.time() * 1000) events = [{"time": now(), "text": "策略启动", "color": "primary"}] while True: account = exchange.GetAccount() if account: low = account["Balance"] < 100 if low: events.insert(0, {"time": now(), "text": "可用资金低于100", "color": "down"}) del events[10:] if low: alert = {"type": "alert", "level": "warning", "title": "可用资金不足", "text": "低于100,暂停开新仓"} else: alert = {"type": "alert", "level": "success", "title": "风控检查通过"} # 用 "\n" 分行:第一行文字加标签,第二行是组件数组(从上到下排列) LogStatus("网格策略", {"type": "badge", "items": [ {"text": "回测" if IsVirtual() else "实盘", "color": "muted"}, {"text": "暂停开仓" if low else "运行中", "color": "warning" if low else "up"} ]}, "\n", [ alert, {"type": "markdown", "text": "#### 策略说明\n- 网格间距 **0.5%**\n- 每格数量 0.01"}, {"type": "timeline", "title": "最近事件", "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("策略启动", "primary")]; loop { if let Ok(a) = exchange.GetAccount() { let low = a.Balance < 100.0; if low { events.insert(0, ev("可用资金低于100", "down")); events.truncate(10); } let mode = if IsVirtual() { "回测" } else { "实盘" }; let (state, color) = if low { ("暂停开仓", "warning") } else { ("运行中", "up") }; let badge = format!(r#"{{"type": "badge", "items": [{}, {}]}}"#, item(mode, "muted"), item(state, color)); let (level, title, text) = match low { true => ("warning", "可用资金不足", "低于100,暂停开新仓"), false => ("success", "风控检查通过", ""), }; let alert = format!( r#"{{"type": "alert", "level": "{}", "title": "{}", "text": "{}"}}"#, level, title, text ); let notes = r"#### 策略说明\n- 网格间距 **0.5%**\n- 每格数量 0.01"; let md = format!(r#"{{"type": "markdown", "text": "{}"}}"#, notes); let tl = format!(r#"{{"type": "timeline", "title": "最近事件", "items": [{}]}}"#, events.join(",")); // 用 "\n" 分行:第一行文字加标签,第二行是组件数组(从上到下排列)(每段 JSON 都写成一行) LogStatus!("网格策略", badge, "\n", format!("[{},{},{}]", alert, md, tl)); } Sleep(10 * 1000); } }
  • 与旧写法混用:反引号表格、按钮和新组件分行输出。

    javascript
    function main() { var account = exchange.GetAccount() if (!account) return var table = {type: "table", title: "账户", cols: ["可用", "冻结"], rows: [[account.Balance, account.FrozenBalance]]} var btn = {type: "button", cmd: "pause", name: "暂停"} LogStatus( "`" + JSON.stringify(table) + "`", // 旧写法:反引号包裹的 JSON,显示不变 "\n汇总", {type: "grid", cols: 3, items: [ // 新组件放在这一行的最后 {type: "kpi", title: "可用", value: account.Balance}, table, btn // 表格、按钮放进 grid 时不用加反引号 ]}, "\n`" + JSON.stringify(btn) + "`" ) // 输出:第一行旧表格;第二行文字加组件网格;第三行按钮,点击后用 GetCommand() 读取 }
    python
    import json def main(): account = exchange.GetAccount() if not account: return table = {"type": "table", "title": "账户", "cols": ["可用", "冻结"], "rows": [[account["Balance"], account["FrozenBalance"]]]} btn = {"type": "button", "cmd": "pause", "name": "暂停"} LogStatus( "`" + json.dumps(table) + "`", # 旧写法:反引号包裹的 JSON,显示不变 "\n汇总", {"type": "grid", "cols": 3, "items": [ # 新组件放在这一行的最后 {"type": "kpi", "title": "可用", "value": account["Balance"]}, table, btn # 表格、按钮放进 grid 时不用加反引号 ]}, "\n`" + json.dumps(btn) + "`" ) # 输出:第一行旧表格;第二行文字加组件网格;第三行按钮,点击后用 GetCommand() 读取
    rust
    fn main() { let Ok(a) = exchange.GetAccount() else { return }; // 反引号里、或和文字同一行的 JSON 要写成一行,去掉换行 let table = format!(r#"{{"type": "table", "title": "账户", "cols": ["可用", "冻结"], "rows": [[{}, {}]]}}"#, a.Balance, a.FrozenBalance).replace('\n', ""); let btn = r#"{"type": "button", "cmd": "pause", "name": "暂停"}"#; let kpi = format!(r#"{{"type": "kpi", "title": "可用", "value": {}}}"#, a.Balance); // 新组件放在这一行的最后;表格、按钮放进 grid 时不用加反引号 let grid = format!(r#"{{"type": "grid", "cols": 3, "items": [{}, {}, {}]}}"#, kpi, table, btn); // 旧写法:反引号包裹的 JSON,显示不变 LogStatus!(format!("`{}`", table), "\n汇总", grid, format!("\n`{}`", btn)); // 输出:第一行旧表格;第二行文字加组件网格;第三行按钮,点击后用 GetCommand() 读取 }
  • 状态栏中数据输出示例。

    javascript
    function main() { var table = {type: 'table', title: 'Position Info', cols: ['Column 1', 'Column 2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]} // JSON序列化后两边加上`字符,视为一个复杂消息格式(当前支持表格) LogStatus('`' + JSON.stringify(table) + '`') // 表格信息也可以在多行中出现 LogStatus('First line message\n`' + JSON.stringify(table) + '`\nThird line message') // 支持多个表格同时显示,将以TAB显示到一组里 LogStatus('`' + JSON.stringify([table, table]) + '`') // 也可以构造一个按钮在表格中,策略用GetCommand接收cmd属性的内容 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) + '`') // 或者构造一单独的按钮 LogStatus('`' + JSON.stringify({'type':'button', 'cmd': 'coverAll', 'name': 'Close All'}) + '`') // 可以自定义按钮风格(bootstrap的按钮属性) 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"]]}"#; // JSON字符串两边加上`字符,视为一个复杂消息格式(当前支持表格) LogStatus!(format!("`{}`", table)); // 表格信息也可以在多行中出现 LogStatus!(format!("First line message\n`{}`\nThird line message", table)); // 支持多个表格同时显示,将以TAB显示到一组里 LogStatus!(format!("`[{},{}]`", table, table)); // 也可以构造一个按钮在表格中,策略用GetCommand接收cmd属性的内容 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)); // 或者构造一单独的按钮 LogStatus!(format!("`{}`", r#"{"type": "button", "cmd": "coverAll", "name": "Close All"}"#)); // 可以自定义按钮风格(bootstrap的按钮属性) LogStatus!(format!("`{}`", r#"{"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "Close All"}"#)); }
  • 支持状态栏中设计按钮控件。

    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)); }
  • 设置状态栏按钮的禁用、描述功能。

    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)); }
  • 结合GetCommand()函数,构造状态栏按钮交互功能。

    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); } }
  • 状态栏表格中的按钮点击后弹出一组输入控件(group),其中价格控件只在选择限价单时显示;GetCommand()收到open:{...}后解析各控件的值。字段说明见上文「状态栏按钮」。

    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"}]} }, { // 只在选择限价单(tradeType为1)时显示 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) { // 例如: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"}]} }, { # 只在选择限价单(tradeType为1)时显示 "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:"): # 例如: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() { // 只在选择限价单(tradeType为1)时显示price控件 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) { // 例如: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); } }

参数

名称类型必填描述

msg

string / number / bool / object / array

否

要显示的内容:文字、数字、表格或按钮对象、状态栏组件对象,或它们组成的数组。可以传多个,用空格拼接;"\n" 换行。

参考

备注

实盘运行时LogStatus()函数输出的信息不写入日志记录,只更新当前实盘的状态栏内容;实盘数据库中只保存最新的一份状态栏内容,每次调用覆盖之前的内容。

输入写法

多个参数先转成文字再用空格拼接:字符串原样,数字转成文字,对象和数组编码成JSON(JavaScript、Python中 type 为 table 的表格对象另加反引号)。拼好的内容按 \n 分行,下表是每一行的显示方式。

写法显示
普通文字文字;结尾写 #ff0000 设置颜色,[trans] 标记按页面语言显示
带 type 的组件对象,或含组件的数组状态栏组件,数组里的组件从上到下排列
反引号包裹的JSON:`{...}`表格、表格数组(显示为标签页)、按钮,也可以是组件
整个状态栏是一段(可以跨多行的)组件JSON整体识别为组件
  • 一行最多一段JSON。不带反引号的组件要放在这一行的最后,前面可以有文字(如 LogStatus("更新时间", kpi));同一行要显示多个组件时放进数组或 grid,或者用 "\n" 分行。
  • 带反引号的JSON前后都可以有文字。一行里已经有反引号时,这一行不带反引号的组件不再识别。
  • 不带反引号时只识别组件:type 不是组件的对象(例如 GetAccount() 的返回值、单独的按钮对象)、只含表格的数组按JSON文字显示,与以前一致。

状态栏组件

组件对象按 type 渲染:grid、tabs 负责布局,kpi、stat、progress、gauge、sparkline、chart、heatmap、calendar、histogram、pie、timeline、markdown、alert、badge 负责展示;表格和按钮对象也可以放进 grid、tabs 的 items。各组件的字段与示例见状态栏组件。

  • grid 按容器宽度自动减少列数,窄屏上也能正常显示。
  • 写错的组件(缺少必填字段、类型不认识、数据超出上限)只在自己的位置显示一张错误卡片,写明出错的字段并附上原始JSON,其它组件照常显示。
  • 配色随页面的明暗主题切换;up、down 跟随页面的涨跌色设置(FMZ默认绿涨红跌)。
  • 与旧写法完全兼容:文字、反引号表格和按钮的显示不变,可以和组件混用。表格还支持列格式、涨跌色与单位,见LogStatus-table。
  • 组件只用于展示,交互用下文的状态栏按钮;按钮可以放进表格单元格,也可以放进 grid、tabs。
  • 上限:图表每条序列5000个点,热力图40000格,grid、tabs 嵌套6层;超出的组件显示错误卡片。

状态栏按钮

把按钮对象JSON.stringify()后用`包裹输出到状态栏,或者放进状态栏表格的单元格(一个单元格放多个按钮时写成按钮数组)。状态栏的每一行只能有一段`包裹的JSON,多个按钮要分行输出或者放进表格。点击按钮后,策略用GetCommand读取命令;实盘没有运行时点击按钮只会提示错误。

按钮字段:

字段说明
type固定为button
name按钮上的文字
cmd点击后发送给策略的命令;为空时点击无效
description鼠标悬停时显示的说明
disabledtrue为禁用
class样式:含btn-primary、btn-success、btn-info、btn-warning、btn-danger时显示对应颜色;含btn-sm、btn-lg时为中、大尺寸,默认小尺寸
input点击后弹出一个输入控件,字段见下文「输入控件」
group点击后弹出一组输入控件,数组,每一项的字段见下文「输入控件」

GetCommand()收到的命令:

按钮命令例子
没有input、groupcmd原样(点击时先弹出确认框)open
有inputcmd:输入值,字符串不加引号,多选为数组open:1、open:abc、open:[0,2]
有groupcmd:JSON,键为各控件的name,没有填写的控件不出现open:{"tradeType":0,"price":100}

输入控件(input或group的每一项)字段:

字段说明
typenumber(数值)、string(字符串)、selected(下拉框)、boolean(开关)
namegroup中为该控件在命令JSON里的键名;input中没有label时用作标题
label控件标题,不设置时使用description
description控件说明
default默认值,也可以写作defValue;下拉框的默认值为选项的下标或value
requiredtrue表示必填
settings控件配置,见下表

settings字段:

字段说明
options下拉框选项:["A", "B"](命令中为选项下标)或[{"name": "A", "value": "a"}](命令中为value)
multiple下拉框多选,命令中的值为数组
customizable下拉框允许输入新选项,选中新选项时命令中为输入的文字
filter显示条件表达式,引用同一组中其它控件的name,例如"tradeType==1";表达式为假时隐藏该控件
disabled禁用该控件
min、maxnumber的最小值、最大值
stepnumber使用滑动条时的步长
unit显示在输入框后的单位文字
render控件外观。number:slider(滑动条)、date(日期时间选择,值为毫秒时间戳);string:textarea(多行)、date(值为yyyy-MM-dd hh:mm:ss)、color(值为#FF00FF);selected:segment(分段选择);boolean只有开关

不带settings的旧写法仍然可用:下拉框用defValue: "A|B|C"设置选项(命令中为下标),name写成"price@tradeType==1"设置显示条件。

按钮文字、控件标题与说明、下拉框选项、表格标题与列名支持中英文双语,写作"中文|English",按页面语言显示;状态栏普通文本与表格单元格中的双语文本使用[trans]中文|English[/trans]。

按钮也可以放进grid、tabs的 items,和状态栏组件排在一起,此时不用加反引号。

LogStatus()函数支持打印base64编码后的图片,以`开头,以`结尾。例如: LogStatus("`data:image/png;base64,AAAA`")。

策略实盘运行时,在实盘页面如果翻看历史记录,状态栏会进入休眠状态,停止更新。只有日志在第一页的时候状态栏数据才会刷新。支持在状态栏输出base64编码后的图片,也支持在状态栏显示的表格中输出base64编码后的图片。由于编码后的图片的字符串数据一般很长,所以不再展示范例代码。

Rust没有对象字面量:把组件写成JSON文本(用 format! 拼接时 {、} 写成 {{、}}),或者传 JSONParse() 得到的 JsonValue,前端同样识别。字符串参数不会自动加反引号,单独输出表格时写 format!("`{}`", table);表格放进 grid、tabs 或含组件的数组时不用加。数值要是有限数,NaN 会让整段JSON无法解析而按文字显示。

LogStatus()函数支持直接传入Python的matplotlib.pyplot对象,只要对象包含savefig方法就可以作为参数传入LogStatus()函数,例如:

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