LogStatus
刷新回测或实盘页面的状态栏:输出文字、表格、按钮,或带 type 的组件对象(指标卡、图表、标签页等);每次调用替换上一次的内容。
LogStatus(...msgs)示例
-
支持设置输出内容的颜色。
javascriptfunction 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') }pythondef 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')rustfn 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"); } -
直接传组件对象:文字后面跟一张最新价指标卡。
javascriptfunction main() { var ticker = exchange.GetTicker() if (!ticker) return // 组件对象直接传,不用 JSON.stringify,也不用加反引号 LogStatus("更新时间 " + _D(), {type: "kpi", title: "最新价", value: ticker.Last}) // 输出:一行「更新时间 …」文字,后面是最新价指标卡 }pythondef main(): ticker = exchange.GetTicker() if not ticker: return # 组件字典直接传,不用 json.dumps,也不用加反引号 LogStatus("更新时间 " + _D(), {"type": "kpi", "title": "最新价", "value": ticker["Last"]}) # 输出:一行「更新时间 …」文字,后面是最新价指标卡rustfn 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排列指标卡、权益曲线和持仓表。
javascriptfunction 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) } }pythonimport 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)rustfn 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(×), join(&curve), rows.join(","))); } Sleep(60 * 1000); } } -
表格增强:列设置格式、涨跌色和单位,点击表头按原始数值排序。
javascriptfunction 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"} ]}) // 输出:盈亏、收益率按正负着色,排序按数值而不是文字 }pythondef 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"} ]}) # 输出:盈亏、收益率按正负着色,排序按数值而不是文字rustfn 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分页显示行情、持仓和账户,刷新时保持当前选中的页。
javascriptfunction 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) } }pythondef 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)rustfn 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。
javascriptfunction 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)]} ]}) }pythondef 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]]} ]})rustfn 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事件。
javascriptfunction 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) } }pythonimport 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)rustfn 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); } } -
与旧写法混用:反引号表格、按钮和新组件分行输出。
javascriptfunction 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() 读取 }pythonimport 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() 读取rustfn 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() 读取 } -
状态栏中数据输出示例。
javascriptfunction 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'}) + '`') }pythonimport 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"}) + '`')rustfn 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"}"#)); } -
支持状态栏中设计按钮控件。
javascriptfunction 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) + "`") }pythonimport 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) + "`")rustfn 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)); } -
设置状态栏按钮的禁用、描述功能。
javascriptfunction 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) + "`") }pythonimport 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) + "`")rustfn 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()函数,构造状态栏按钮交互功能。javascriptfunction 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) } }pythonimport 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)rustfn 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:{...}后解析各控件的值。字段说明见上文「状态栏按钮」。javascriptfunction 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) } }pythonimport 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)rustfn 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 | 否 | 要显示的内容:文字、数字、表格或按钮对象、 |
参考
备注
实盘运行时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 | 鼠标悬停时显示的说明 |
| disabled | true为禁用 |
| class | 样式:含btn-primary、btn-success、btn-info、btn-warning、btn-danger时显示对应颜色;含btn-sm、btn-lg时为中、大尺寸,默认小尺寸 |
| input | 点击后弹出一个输入控件,字段见下文「输入控件」 |
| group | 点击后弹出一组输入控件,数组,每一项的字段见下文「输入控件」 |
GetCommand()收到的命令:
| 按钮 | 命令 | 例子 |
|---|---|---|
没有input、group | cmd原样(点击时先弹出确认框) | open |
有input | cmd:输入值,字符串不加引号,多选为数组 | open:1、open:abc、open:[0,2] |
有group | cmd:JSON,键为各控件的name,没有填写的控件不出现 | open:{"tradeType":0,"price":100} |
输入控件(input或group的每一项)字段:
| 字段 | 说明 |
|---|---|
| type | number(数值)、string(字符串)、selected(下拉框)、boolean(开关) |
| name | group中为该控件在命令JSON里的键名;input中没有label时用作标题 |
| label | 控件标题,不设置时使用description |
| description | 控件说明 |
| default | 默认值,也可以写作defValue;下拉框的默认值为选项的下标或value |
| required | true表示必填 |
| settings | 控件配置,见下表 |
settings字段:
| 字段 | 说明 |
|---|---|
| options | 下拉框选项:["A", "B"](命令中为选项下标)或[{"name": "A", "value": "a"}](命令中为value) |
| multiple | 下拉框多选,命令中的值为数组 |
| customizable | 下拉框允许输入新选项,选中新选项时命令中为输入的文字 |
| filter | 显示条件表达式,引用同一组中其它控件的name,例如"tradeType==1";表达式为假时隐藏该控件 |
| disabled | 禁用该控件 |
| min、max | number的最小值、最大值 |
| step | number使用滑动条时的步长 |
| 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)