Log
Log
The Log() function is used to output logs.
Log(...msgs)Examples
-
Multiple
msgparameters can be passed in:javascriptfunction main() { Log("msg1", "msg2", "msg3") }pythondef main(): Log("msg1", "msg2", "msg3")rustfn main() { Log!("msg1", "msg2", "msg3"); }c++void main() { Log("msg1", "msg2", "msg3"); } -
Setting the color of the output message is supported. If you need to set both the color and push simultaneously, set the color first and then use the
@character to set push at the end.javascriptfunction main() { Log("Hello FMZ Quant !@") Sleep(1000 * 5) // Adding #ff0000 within the string makes the printed log display in red and pushes the message Log("Hello, #ff0000@") }pythondef main(): Log("Hello FMZ Quant !@") Sleep(1000 * 5) Log("Hello, #ff0000@")rustfn main() { Log!("Hello FMZ Quant !@"); Sleep(1000 * 5); // Adding #ff0000 within the string makes the printed log display in red and pushes the message Log!("Hello, #ff0000@"); }c++void main() { Log("Hello FMZ Quant !@"); Sleep(1000 * 5); Log("Hello, #ff0000@"); } -
The
Log()function supports printingbase64-encoded images. The content starts with`and ends with`, for example:javascriptfunction main() { Log("`data:image/png;base64,AAAA`") }pythondef main(): Log("`data:image/png;base64,AAAA`")rustfn main() { Log!("`data:image/png;base64,AAAA`"); }c++void main() { Log("`data:image/png;base64,AAAA`"); } -
The
Log()function supports directly printingPython'smatplotlib.pyplotobject. As long as the object contains asavefigmethod, it can be printed directly using theLogfunction, for example:pythonimport matplotlib.pyplot as plt def main(): plt.plot([3,6,2,4,7,1]) Log(plt) -
The
Log()function supports language switching. Its output text will automatically switch to the corresponding language according to the language setting of the platform page, for example:javascriptfunction main() { Log("[trans]中文|abc[/trans]") }pythondef main(): Log("[trans]中文|abc[/trans]")rustfn main() { Log!("[trans]中文|abc[/trans]"); }c++void main() { Log("[trans]中文|abc[/trans]"); }
Arguments
| Name | Type | Required | Description |
msg | string / number / bool / object / array / any (any type supported by the platform) | No | The parameter |
See Also
Remarks
The Log() function outputs a log message to the log area of the live trading or backtesting system. During live trading, the logs will be saved in the live trading database. If the content output by the Log() function ends with the @ character, that log will enter the message push queue and be pushed to the email address, WebHook address, etc. configured in the Push Settings of the current FMZ Quant Trading Platform account. The Debugging Tool and the backtesting system do not support message push. Message push is subject to frequency limits, with the specific rules as follows: within each 20-second cycle of live trading, only the last push message is retained and pushed, while the rest are filtered out and not pushed (the push logs output via the Log function are still printed and displayed normally in the log area).
If the content output by the Log() function ends with the & character, that log will be marked as a private log. When the live trading is publicly displayed, that log is hidden from other users, but remains visible from the perspective of the live trading owner's account. This feature can be used to record sensitive information such as API keys, account balances, etc. For example: Log("Private information", "&").
Regarding WebHook push, you can use a service program written in Golang:
golang
package main
import (
"fmt"
"net/http"
)
func Handle (w http.ResponseWriter, r *http.Request) {
defer func() {
fmt.Println("req:", *r)
}()
}
func main () {
fmt.Println("listen http://localhost:9090")
http.HandleFunc("/data", Handle)
http.ListenAndServe(":9090", nil)
}
Set the WebHook in the Push Settings: http://XXX.XX.XXX.XX:9090/data?data=Hello_FMZ. After running the written Golang service program, you can start running the live trading strategy. The following is a strategy written in JavaScript; when the strategy runs, it executes the Log() function and pushes the message:
javascript
function main() {
Log("msg", "@")
}
After the service program written in Golang receives the push, it prints the following information:
log
listen http://localhost:9090
req: {GET /data?data=Hello_FMZ HTTP/1.1 1 1
map[User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/xx.x.xxxx.xxx
Safari/537.36] Accept-Encoding:[gzip]] {} <nil> 0 [] false
1XX.XX.X.XX:9090 map[] map[] <nil> map[] XXX.XX.XXX.XX:4xxx2
/data?data=Hello_FMZ <nil> <nil> <nil> 0xc420056300
LogProfit
Records and prints the profit/loss value, and plots the equity curve based on the profit/loss value.
LogProfit(profit)
LogProfit(profit, ...args)Examples
When calling the LogProfit function, if the last parameter is the character &, the log will not be written to the database and only the profit chart will be updated. Using the & parameter can avoid generating a large number of logs from frequent profit records, thereby keeping the logs clean. For example:
javascript
function main() {
// Plot 30 points on the profit chart
for(var i = 0; i < 30; i++) {
LogProfit(i, '&')
Sleep(500)
}
}
python
def main():
for i in range(30):
LogProfit(i, '&')
Sleep(500)
rust
fn main() {
// Plot 30 points on the profit chart
// In Rust, LogProfit only accepts the profit value parameter and does not support extended parameters such as '&'
for i in 0..30 {
LogProfit(i);
Sleep(500);
}
}
c++
void main() {
for(int i = 0; i < 30; i++) {
LogProfit(i, '&');
Sleep(500);
}
}Arguments
| Name | Type | Required | Description |
profit | number | Yes | The parameter |
arg | string / number / bool / object / array / any (any type supported by the platform) | No | Extended parameter, used to output additional information to this profit log. Multiple |
See Also
LogProfitReset
Clear all profit logs and the profit chart.
LogProfitReset()
LogProfitReset(remain)Examples
javascript
function main() {
// Print 30 data points on the profit chart, then reset, keeping only the last 10 data points
for(var i = 0; i < 30; i++) {
LogProfit(i)
Sleep(500)
}
LogProfitReset(10)
}
python
def main():
for i in range(30):
LogProfit(i)
Sleep(500)
LogProfitReset(10)
rust
fn main() {
// Print 30 data points on the profit chart, then reset, keeping only the last 10 data points
for i in 0..30 {
LogProfit(i);
Sleep(500);
}
LogProfitReset(10);
}
c++
void main() {
for(int i = 0; i < 30; i++) {
LogProfit(i);
Sleep(500);
}
LogProfitReset(10);
}Arguments
| Name | Type | Required | Description |
remain | number | No | The |
See Also
LogStatus
Outputs information to the status bar on the backtesting system or the live trading page.
LogStatus(...msgs)Examples
-
Supports setting the color of the output content:
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"); }c++void 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"); } -
Example of data output in the status bar:
javascriptfunction 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'}) + '`') }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"]]}"#; // 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"}"#)); }c++void main() { json table = R"({"type": "table", "title": "Position Info", "cols": ["Column 1", "Column 2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]})"_json; LogStatus("`" + table.dump() + "`"); LogStatus("First line message\n`" + table.dump() + "`\nThird line message"); json arr = R"([])"_json; arr.push_back(table); arr.push_back(table); LogStatus("`" + arr.dump() + "`"); table = R"({ "type" : "table", "title" : "Position Operation", "cols" : ["Column 1", "Column 2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "Close All"}] ] })"_json; LogStatus("`" + table.dump() + "`"); LogStatus("`" + R"({"type": "button", "cmd": "coverAll", "name": "Close All"})"_json.dump() + "`"); LogStatus("`" + R"({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "Close All"})"_json.dump() + "`"); } -
Supports designing button controls in the status bar (legacy button structure):
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)); }c++void main() { json table = R"({ "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"} ] ] })"_json; LogStatus("`" + table.dump() + "`"); } -
Configure the disable and description features of status bar buttons (legacy button structure):
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)); }c++void main() { json table = R"({ "type": "table", "title": "Status Bar Button Disable and Description Test", "cols": ["Column 1", "Column 2", "Column 3"], "rows": [] })"_json; json button1 = R"({"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"})"_json; json button2 = R"({"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true})"_json; json button3 = R"({"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false})"_json; json arr = R"([])"_json; arr.push_back(button1); arr.push_back(button2); arr.push_back(button3); table["rows"].push_back(arr); LogStatus("`" + table.dump() + "`"); } -
In combination with the
GetCommand()function, build the interactive functionality of status bar buttons (legacy button structure):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(0) { Log!("Received interaction data str_cmd:", "Type:", "String", "Value:", &str_cmd); if str_cmd == "test1" { test1(); } } Sleep(500); } }c++void test1() { Log("Calling custom function"); } void main() { while(true) { json table = R"({ "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": "", "name": "Call Function" }], ["d", "1", { "type": "button", "cmd": "test1", "name": "Call Custom Function" }] ] })"_json; table["rows"][2][2]["cmd"] = _D(); LogStatus(_D(), "\n", "`" + table.dump() + "`"); auto str_cmd = GetCommand(); if(str_cmd != "") { Log("Received interaction data str_cmd", "Type:", typeid(str_cmd).name(), "Value:", str_cmd); if(str_cmd == "test1") { test1(); } } Sleep(500); } } -
When constructing status bar buttons for interaction, data input is also supported, and the interaction command is ultimately captured by the
GetCommand()function.By adding an
inputfield to the data structure of a status bar button control (legacy button structure) — for example, adding"input": {"name": "Quantity", "type": "number", "defValue": 1}to{"type": "button", "cmd": "open", "name": "Open"}— the button, when clicked, will pop up a dialog containing an input field control (the default value in the input field is 1, i.e. the value set bydefValue), so that a value can be entered and sent together with the button command. For example, when running the following test code, clicking the "Open Position" button will pop up a dialog with an input field; entering 111 in the input field and clicking "OK" will cause theGetCommand()function to capture the message:open:111.javascriptfunction main() { var tbl = { type: "table", title: "Operation", cols: ["Column 1", "Column 2"], rows: [ ["Open Position", {"type": "button", "cmd": "open", "name": "Open", "input": {"name": "Quantity", "type": "number", "defValue": 1}}], ["Close Position", {"type": "button", "cmd": "coverAll", "name": "Close All"}] ] } LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`") while (true) { var cmd = GetCommand() if (cmd) { Log("cmd:", cmd) } Sleep(1000) } }pythonimport json def main(): tbl = { "type": "table", "title": "Operation", "cols": ["Column 1", "Column 2"], "rows": [ ["Open Position", {"type": "button", "cmd": "open", "name": "Open", "input": {"name": "Quantity", "type": "number", "defValue": 1}}], ["Close Position", {"type": "button", "cmd": "coverAll", "name": "Close All"}] ] } LogStatus(_D(), "\n", "`" + json.dumps(tbl) + "`") while True: cmd = GetCommand() if cmd: Log("cmd:", cmd) Sleep(1000)rustfn main() { let tbl = String::from(r#"{"type": "table", "title": "Operation", "cols": ["Column 1", "Column 2"], "rows": ["#) + r#"["Open Position", {"type": "button", "cmd": "open", "name": "Open", "input": {"name": "Quantity", "type": "number", "defValue": 1}}],"# + r#"["Close Position", {"type": "button", "cmd": "coverAll", "name": "Close All"}]"# + r#"]}"#; LogStatus!(_D(None), "\n", format!("`{}`", tbl)); loop { if let Some(cmd) = GetCommand(0) { Log!("cmd:", cmd); } Sleep(1000); } }c++void main() { json tbl = R"({ "type": "table", "title": "Operation", "cols": ["Column 1", "Column 2"], "rows": [ ["Open Position", {"type": "button", "cmd": "open", "name": "Open", "input": {"name": "Quantity", "type": "number", "defValue": 1}}], ["Close Position", {"type": "button", "cmd": "coverAll", "name": "Close All"}] ] })"_json; LogStatus(_D(), "\n", "`" + tbl.dump() + "`"); while(true) { auto cmd = GetCommand(); if(cmd != "") { Log("cmd:", cmd); } Sleep(1000); } } -
Group button controls are supported (the legacy button structure). Their functionality is identical to the status bar button that supports data input (configured via the "input" field), and the interaction commands are ultimately captured by the
GetCommand()function. The difference is that group buttons are configured via the"group"field: when a button is clicked to trigger an interaction, the dialog box that pops up on the page displays a pre-configured set of input controls, allowing a group of data to be entered all at once.
Regarding the"group"field in the structure of status bar button controls and group button controls, note the following points:- The
typeproperty in group only supports the following 4 types, and thedefValueproperty is used to set the default value.
"selected": drop-down box control; use the|symbol to separate the options in the drop-down box.
"number": numeric input box control.
"string": string input box control.
"boolean": checkbox control; checked means (boolean) true, unchecked means (boolean) false. - Controls for interactive input support dependency settings:
For example, the"name": "tradePrice@orderType==1"setting in the example below makes the trade price (tradePrice) input control available only when the order type (orderType) drop-down box control is set to limit order. - The names of controls for interactive input support bilingual settings.
For example, the "description": "下单方式|order type" setting in the example below uses the|symbol to separate the Chinese and English description content. - Although the
nameanddescriptionin group share the same field names as thenameanddescriptionin the button structure, their definitions are not the same.
Thenamein group is also defined differently from thenamein input. - After a group button control is triggered, the format of the interaction content sent is: the button's cmd field value plus the group field-related data. For example, when testing the example below, the content output by the
Log("cmd:", cmd)statement is:
cmd: open:{"orderType":1,"tradePrice":99,"orderAmount":"99","boolean":true}, that is, the content returned by theGetCommand()function when the interaction operation occurs:open:{"orderType":1,"tradePrice":99,"orderAmount":"99","boolean":true}. - The
typeproperty of button controls only supports"button":
For button controls that support data input, i.e. controls with theinputproperty set, thetypeproperty in theinputfield configuration supports multiple control types.
Refer to the following example:
javascriptfunction main() { var tbl = { type: "table", title: "Group Button Control Demo", cols: ["Operation"], rows: [] } // Create the group button control structure var groupBtn = { type: "button", cmd: "open", name: "Open", group: [ {"name": "orderType", "description": "下单方式|order type", "type": "selected", "defValue": "市价单|挂单"}, {"name": "tradePrice@orderType==1", "description": "交易价格|trade price", "type": "number", "defValue": 100}, {"name": "orderAmount", "description": "委托数量|order amount", "type": "string", "defValue": 100}, {"name": "boolean", "description": "是/否|boolean", "type": "boolean", "defValue": true} ] } // Test button 1 var testBtn1 = {"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"} var testBtn2 = {"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button", "input": {"name": "Quantity", "type": "number", "defValue": 1}} // Add groupBtn to tbl tbl.rows.push([groupBtn]) // A single cell of a status bar table supports setting multiple buttons, i.e. the data in a single cell is an array of button structures: [testBtn1, testBtn2] tbl.rows.push([[testBtn1, testBtn2]]) while (true) { LogStatus("`" + JSON.stringify(tbl) + "`", "\n", "Group button controls can be set directly on the status bar in addition to status bar tables:", "`" + JSON.stringify(groupBtn) + "`") var cmd = GetCommand() if (cmd) { Log("cmd:", cmd) } Sleep(5000) } }pythonimport json def main(): tbl = { "type": "table", "title": "Group Button Control Demo", "cols": ["Operation"], "rows": [] } groupBtn = { "type": "button", "cmd": "open", "name": "Open", "group": [ {"name": "orderType", "description": "下单方式|order type", "type": "selected", "defValue": "市价单|挂单"}, {"name": "tradePrice@orderType==1", "description": "交易价格|trade price", "type": "number", "defValue": 100}, {"name": "orderAmount", "description": "委托数量|order amount", "type": "string", "defValue": 100}, {"name": "boolean", "description": "是/否|boolean", "type": "boolean", "defValue": True} ] } testBtn1 = {"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"} testBtn2 = {"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button", "input": {"name": "Quantity", "type": "number", "defValue": 1}} tbl["rows"].append([groupBtn]) tbl["rows"].append([[testBtn1, testBtn2]]) while True: LogStatus("`" + json.dumps(tbl) + "`", "\n", "Group button controls can be set directly on the status bar in addition to status bar tables:", "`" + json.dumps(groupBtn) + "`") cmd = GetCommand() if cmd: Log("cmd:", cmd) Sleep(5000)rustfn main() { // Create the group button control structure let group_btn = String::from(r#"{"type": "button", "cmd": "open", "name": "Open", "group": ["#) + r#"{"name": "orderType", "description": "下单方式|order type", "type": "selected", "defValue": "市价单|挂单"},"# + r#"{"name": "tradePrice@orderType==1", "description": "交易价格|trade price", "type": "number", "defValue": 100},"# + r#"{"name": "orderAmount", "description": "委托数量|order amount", "type": "string", "defValue": 100},"# + r#"{"name": "boolean", "description": "是/否|boolean", "type": "boolean", "defValue": true}"# + r#"]}"#; // Test button 1, test button 2 let test_btn1 = r#"{"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"}"#; let test_btn2 = r#"{"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button", "input": {"name": "Quantity", "type": "number", "defValue": 1}}"#; // Add groupBtn to tbl; a single cell of a status bar table supports setting multiple buttons, i.e. the data in a single cell is an array of button structures: [testBtn1, testBtn2] let tbl = format!( r#"{{"type": "table", "title": "Group Button Control Demo", "cols": ["Operation"], "rows": [[{}], [[{}, {}]]]}}"#, group_btn, test_btn1, test_btn2 ); loop { LogStatus!(format!("`{}`", tbl), "\n", "Group button controls can be set directly on the status bar in addition to status bar tables:", format!("`{}`", group_btn)); if let Some(cmd) = GetCommand(0) { Log!("cmd:", cmd); } Sleep(5000); } }c++void main() { json tbl = R"({ "type": "table", "title": "Group Button Control Demo", "cols": ["Operation"], "rows": [] })"_json; json groupBtn = R"({ "type": "button", "name": "Open", "cmd": "open", "group": [ {"name": "orderType", "description": "下单方式|order type", "type": "selected", "defValue": "市价单|挂单"}, {"name": "tradePrice@orderType==1", "description": "交易价格|trade price", "type": "number", "defValue": 100}, {"name": "orderAmount", "description": "委托数量|order amount", "type": "string", "defValue": 100}, {"name": "boolean", "description": "是/否|boolean", "type": "boolean", "defValue": true} ]})"_json; json testBtn1 = R"({"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"})"_json; json testBtn2 = R"({"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button", "input": {"name": "Quantity", "type": "number", "defValue": 1}})"_json; tbl["rows"].push_back({groupBtn}); tbl["rows"].push_back({{testBtn1, testBtn2}}); while(true) { LogStatus("`" + tbl.dump() + "`", "\n", "Group button controls can be set directly on the status bar in addition to status bar tables:", "`" + groupBtn.dump() + "`"); auto cmd = GetCommand(); if(cmd != "") { Log("cmd:", cmd); } Sleep(5000); } } - The
-
When the status bar group button control (implemented by setting the
groupfield) and the status bar button control (implemented by setting theinputfield) are clicked to trigger interaction (legacy button structure), the dropdown control in the dialog box that pops up on the page also supports multi-select. The following example demonstrates how to design a dropdown control with multi-select options:javascriptfunction main() { // In the page triggered by the status bar button control (implemented by setting the input field) testBtn1 button, the dropdown control uses the options field to set options, and uses the defValue field to set the default option. This differs from the approach in other examples of this chapter that set options directly using defValue. var testBtn1 = { type: "button", name: "testBtn1", cmd: "cmdTestBtn1", input: {name: "testBtn1ComboBox", type: "selected", options: ["A", "B"], defValue: 1} } /* In the page triggered by the status bar button control (implemented by setting the input field) testBtn2 button, the dropdown control uses the options field to set options. The options in the options field support not only strings, but also the ```{text: "description", value: "value"}``` structure. Use the defValue field to set the default option; the default option supports multi-select (implemented via an array structure). For multi-select, you need to additionally set the multiple field to a truthy value (true). */ var testBtn2 = { type: "button", name: "testBtn2", cmd: "cmdTestBtn2", input: { name: "testBtn2MultiComboBox", type: "selected", description: "Implement multi-select dropdown", options: [{text: "Option A", value: "A"}, {text: "Option B", value: "B"}, {text: "Option C", value: "C"}], defValue: ["A", "C"], multiple: true } } // In the page triggered by the status bar group button control (implemented by setting the group field) testBtn3 button, the dropdown control uses the options field to set options, and also supports setting options directly using defValue. var testBtn3 = { type: "button", name: "testBtn3", cmd: "cmdTestBtn3", group: [ {name: "comboBox1", label: "labelComboBox1", description: "Dropdown 1", type: "selected", defValue: 1, options: ["A", "B"]}, {name: "comboBox2", label: "labelComboBox2", description: "Dropdown 2", type: "selected", defValue: "A|B"}, {name: "comboBox3", label: "labelComboBox3", description: "Dropdown 3", type: "selected", defValue: [0, 2], multiple: true, options: ["A", "B", "C"]}, { name: "comboBox4", label: "labelComboBox4", description: "Dropdown 4", type: "selected", defValue: ["A", "C"], multiple: true, options: [{text: "Option A", value: "A"}, {text: "Option B", value: "B"}, {text: "Option C", value: "C"}, {text: "Option D", value: "D"}] } ] } while (true) { LogStatus("`" + JSON.stringify(testBtn1) + "`\n", "`" + JSON.stringify(testBtn2) + "`\n", "`" + JSON.stringify(testBtn3) + "`\n") var cmd = GetCommand() if (cmd) { Log(cmd) } Sleep(5000) } }pythonimport json def main(): testBtn1 = { "type": "button", "name": "testBtn1", "cmd": "cmdTestBtn1", "input": {"name": "testBtn1ComboBox", "type": "selected", "options": ["A", "B"], "defValue": 1} } testBtn2 = { "type": "button", "name": "testBtn2", "cmd": "cmdTestBtn2", "input": { "name": "testBtn2MultiComboBox", "type": "selected", "description": "Implement multi-select dropdown", "options": [{"text": "Option A", "value": "A"}, {"text": "Option B", "value": "B"}, {"text": "Option C", "value": "C"}], "defValue": ["A", "C"], "multiple": True } } testBtn3 = { "type": "button", "name": "testBtn3", "cmd": "cmdTestBtn3", "group": [ {"name": "comboBox1", "label": "labelComboBox1", "description": "Dropdown 1", "type": "selected", "defValue": 1, "options": ["A", "B"]}, {"name": "comboBox2", "label": "labelComboBox2", "description": "Dropdown 2", "type": "selected", "defValue": "A|B"}, {"name": "comboBox3", "label": "labelComboBox3", "description": "Dropdown 3", "type": "selected", "defValue": [0, 2], "multiple": True, "options": ["A", "B", "C"]}, { "name": "comboBox4", "label": "labelComboBox4", "description": "Dropdown 4", "type": "selected", "defValue": ["A", "C"], "multiple": True, "options": [{"text": "Option A", "value": "A"}, {"text": "Option B", "value": "B"}, {"text": "Option C", "value": "C"}, {"text": "Option D", "value": "D"}] } ] } while True: LogStatus("`" + json.dumps(testBtn1) + "`\n", "`" + json.dumps(testBtn2) + "`\n", "`" + json.dumps(testBtn3) + "`\n") cmd = GetCommand() if cmd: Log(cmd) Sleep(5000)rustfn main() { // In the page triggered by the status bar button control (implemented by setting the input field) testBtn1 button, the dropdown control uses the options field to set options, and uses the defValue field to set the default option. This differs from the approach in other examples of this chapter that set options directly using defValue. let test_btn1 = r#"{"type": "button", "name": "testBtn1", "cmd": "cmdTestBtn1", "input": {"name": "testBtn1ComboBox", "type": "selected", "options": ["A", "B"], "defValue": 1}}"#; /* In the page triggered by the status bar button control (implemented by setting the input field) testBtn2 button, the dropdown control uses the options field to set options. The options in the options field support not only strings, but also the {"text": "description", "value": "value"} structure. Use the defValue field to set the default option; the default option supports multi-select (implemented via an array structure). For multi-select, you need to additionally set the multiple field to a truthy value (true). */ let test_btn2 = String::from(r#"{"type": "button", "name": "testBtn2", "cmd": "cmdTestBtn2", "input": {"#) + r#""name": "testBtn2MultiComboBox", "type": "selected", "description": "Implement multi-select dropdown","# + r#""options": [{"text": "Option A", "value": "A"}, {"text": "Option B", "value": "B"}, {"text": "Option C", "value": "C"}],"# + r#""defValue": ["A", "C"], "multiple": true}}"#; // In the page triggered by the status bar group button control (implemented by setting the group field) testBtn3 button, the dropdown control uses the options field to set options, and also supports setting options directly using defValue. let test_btn3 = String::from(r#"{"type": "button", "name": "testBtn3", "cmd": "cmdTestBtn3", "group": ["#) + r#"{"name": "comboBox1", "label": "labelComboBox1", "description": "Dropdown 1", "type": "selected", "defValue": 1, "options": ["A", "B"]},"# + r#"{"name": "comboBox2", "label": "labelComboBox2", "description": "Dropdown 2", "type": "selected", "defValue": "A|B"},"# + r#"{"name": "comboBox3", "label": "labelComboBox3", "description": "Dropdown 3", "type": "selected", "defValue": [0, 2], "multiple": true, "options": ["A", "B", "C"]},"# + r#"{"name": "comboBox4", "label": "labelComboBox4", "description": "Dropdown 4", "type": "selected", "defValue": ["A", "C"], "multiple": true, "options": [{"text": "Option A", "value": "A"}, {"text": "Option B", "value": "B"}, {"text": "Option C", "value": "C"}, {"text": "Option D", "value": "D"}]}"# + r#"]}"#; loop { LogStatus!(format!("`{}`\n", test_btn1), format!("`{}`\n", test_btn2), format!("`{}`\n", test_btn3)); if let Some(cmd) = GetCommand(0) { Log!(cmd); } Sleep(5000); } }c++void main() { json testBtn1 = R"({ "type": "button", "name": "testBtn1", "cmd": "cmdTestBtn1", "input": {"name": "testBtn1ComboBox", "type": "selected", "options": ["A", "B"], "defValue": 1} })"_json; json testBtn2 = R"({ "type": "button", "name": "testBtn2", "cmd": "cmdTestBtn2", "input": { "name": "testBtn2MultiComboBox", "type": "selected", "description": "Implement multi-select dropdown", "options": [{"text": "Option A", "value": "A"}, {"text": "Option B", "value": "B"}, {"text": "Option C", "value": "C"}], "defValue": ["A", "C"], "multiple": true } })"_json; json testBtn3 = R"({ "type": "button", "name": "testBtn3", "cmd": "cmdTestBtn3", "group": [ {"name": "comboBox1", "label": "labelComboBox1", "description": "Dropdown 1", "type": "selected", "defValue": 1, "options": ["A", "B"]}, {"name": "comboBox2", "label": "labelComboBox2", "description": "Dropdown 2", "type": "selected", "defValue": "A|B"}, {"name": "comboBox3", "label": "labelComboBox3", "description": "Dropdown 3", "type": "selected", "defValue": [0, 2], "multiple": true, "options": ["A", "B", "C"]}, { "name": "comboBox4", "label": "labelComboBox4", "description": "Dropdown 4", "type": "selected", "defValue": ["A", "C"], "multiple": true, "options": [{"text": "Option A", "value": "A"}, {"text": "Option B", "value": "B"}, {"text": "Option C", "value": "C"}, {"text": "Option D", "value": "D"}] } ] })"_json; while (true) { LogStatus("`" + testBtn1.dump() + "`\n", "`" + testBtn2.dump() + "`\n", "`" + testBtn3.dump() + "`\n"); auto cmd = GetCommand(); if (cmd != "") { Log(cmd); } Sleep(5000); } } -
Based on the current latest button structure, construct the buttons in the status bar table; when clicking a button triggers an interaction, pop up a dialog box containing multiple controls.
For more details, refer to: User Guide - Interactive Controls in the Status Bar.
javascriptvar symbols = ["BTC_USDT.swap", "ETH_USDT.swap", "LTC_USDT.swap", "BNB_USDT.swap", "SOL_USDT.swap"] function createBtn(tmp, group) { var btn = JSON.parse(JSON.stringify(tmp)) _.each(group, function(eleByGroup) { btn["group"].unshift(eleByGroup) }) return btn } function main() { var arrManager = [] _.each(symbols, function(symbol) { arrManager.push({ "symbol": symbol, }) }) // Btn var tmpBtnOpen = { "type": "button", "cmd": "open", "name": "Open Position", "group": [{ "type": "selected", "name": "tradeType", "label": "Order Type", "description": "Market order, Limit order", "default": 0, "group": "Trade Settings", "settings": { "options": ["Market Order", "Limit Order"], "required": true, } }, { "type": "selected", "name": "direction", "label": "Trade Direction", "description": "Buy, Sell", "default": "buy", "group": "Trade Settings", "settings": { "render": "segment", "required": true, "options": [{"name": "Buy", "value": "buy"}, {"name": "Sell", "value": "sell"}], } }, { "type": "number", "name": "price", "label": "Price", "description": "Order price", "group": "Trade Settings", "filter": "tradeType==1", "settings": { "required": true, } }, { "type": "number", "name": "amount", "label": "Order Amount", "description": "Order amount", "group": "Trade Settings", "settings": { "required": true, } }], } while (true) { var tbl = {"type": "table", "title": "dashboard", "cols": ["symbol", "actionOpen"], "rows": []} _.each(arrManager, function(m) { var btnOpen = createBtn(tmpBtnOpen, [{"type": "string", "name": "symbol", "label": "Symbol", "default": m["symbol"], "settings": {"required": true}}]) tbl["rows"].push([m["symbol"], btnOpen]) }) var cmd = GetCommand() if (cmd) { Log("Received interaction:", cmd) // Parse the interaction message: open:{"symbol":"LTC_USDT.swap","tradeType":0,"direction":"buy","amount":111} // Determine which button template triggered the message based on the command before the first colon : var arrCmd = cmd.split(":", 2) if (arrCmd[0] == "open") { var msg = JSON.parse(cmd.slice(5)) Log("Symbol:", msg["symbol"], ", Direction:", msg["direction"], ", Order type:", msg["tradeType"] == 0 ? "Market order" : "Limit order", msg["tradeType"] == 0 ? ", Price: Current market price" : ", Price:" + msg["price"], ", Amount:", msg["amount"]) } } LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`") Sleep(1000) } }pythonimport json symbols = ["BTC_USDT.swap", "ETH_USDT.swap", "LTC_USDT.swap", "BNB_USDT.swap", "SOL_USDT.swap"] def createBtn(tmp, group): btn = json.loads(json.dumps(tmp)) for eleByGroup in group: btn["group"].insert(0, eleByGroup) return btn def main(): arrManager = [] for symbol in symbols: arrManager.append({"symbol": symbol}) # Btn tmpBtnOpen = { "type": "button", "cmd": "open", "name": "Open Position", "group": [{ "type": "selected", "name": "tradeType", "label": "Order Type", "description": "Market order, Limit order", "default": 0, "group": "Trade Settings", "settings": { "options": ["Market Order", "Limit Order"], "required": True, } }, { "type": "selected", "name": "direction", "label": "Trade Direction", "description": "Buy, Sell", "default": "buy", "group": "Trade Settings", "settings": { "render": "segment", "required": True, "options": [{"name": "Buy", "value": "buy"}, {"name": "Sell", "value": "sell"}], } }, { "type": "number", "name": "price", "label": "Price", "description": "Order price", "group": "Trade Settings", "filter": "tradeType==1", "settings": { "required": True, } }, { "type": "number", "name": "amount", "label": "Order Amount", "description": "Order amount", "group": "Trade Settings", "settings": { "required": True, } }], } while True: tbl = {"type": "table", "title": "dashboard", "cols": ["symbol", "actionOpen"], "rows": []} for m in arrManager: btnOpen = createBtn(tmpBtnOpen, [{"type": "string", "name": "symbol", "label": "Symbol", "default": m["symbol"], "settings": {"required": True}}]) tbl["rows"].append([m["symbol"], btnOpen]) cmd = GetCommand() if cmd != "" and cmd != None: Log("Received interaction:", cmd) # Parse the interaction message: open:{"symbol":"LTC_USDT.swap","tradeType":0,"direction":"buy","amount":111} # Determine which button template triggered the message based on the command before the first colon : arrCmd = cmd.split(":") if arrCmd[0] == "open": msg = json.loads(cmd[5:]) Log("Symbol:", msg["symbol"], ", Direction:", msg["direction"], ", Order type:", "Market order" if msg["tradeType"] == 0 else "Limit order", ", Price: Current market price" if msg["tradeType"] == 0 else ", Price:" + str(msg["price"]), ", Amount:", msg["amount"]) # Output status bar information LogStatus(_D(), "\n", "`" + json.dumps(tbl) + "`") Sleep(1000)rustfn main() { let symbols = ["BTC_USDT.swap", "ETH_USDT.swap", "LTC_USDT.swap", "BNB_USDT.swap", "SOL_USDT.swap"]; // Btn: button template; the first element of "group" is the trading symbol control, __SYMBOL__ is a placeholder that is replaced when constructing the button let tmp_btn_open = String::from(r#"{"type": "button", "cmd": "open", "name": "Open Position", "group": ["#) + r#"{"type": "string", "name": "symbol", "label": "Symbol", "default": "__SYMBOL__", "settings": {"required": true}},"# + r#"{"type": "selected", "name": "tradeType", "label": "Order Type", "description": "Market order, Limit order", "default": 0, "group": "Trade Settings", "settings": {"options": ["Market Order", "Limit Order"], "required": true}},"# + r#"{"type": "selected", "name": "direction", "label": "Trade Direction", "description": "Buy, Sell", "default": "buy", "group": "Trade Settings", "settings": {"render": "segment", "required": true, "options": [{"name": "Buy", "value": "buy"}, {"name": "Sell", "value": "sell"}]}},"# + r#"{"type": "number", "name": "price", "label": "Price", "description": "Order price", "group": "Trade Settings", "filter": "tradeType==1", "settings": {"required": true}},"# + r#"{"type": "number", "name": "amount", "label": "Order Amount", "description": "Order amount", "group": "Trade Settings", "settings": {"required": true}}"# + r#"]}"#; loop { let mut rows: Vec<String> = Vec::new(); for symbol in &symbols { let btn_open = tmp_btn_open.replace("__SYMBOL__", symbol); rows.push(format!(r#"["{}", {}]"#, symbol, btn_open)); } let tbl = format!(r#"{{"type": "table", "title": "dashboard", "cols": ["symbol", "actionOpen"], "rows": [{}]}}"#, rows.join(",")); if let Some(cmd) = GetCommand(0) { Log!("Received interaction:", &cmd); // Parse the interaction message: open:{"symbol":"LTC_USDT.swap","tradeType":0,"direction":"buy","amount":111} // Determine which button template triggered the message based on the command before the first colon : if cmd.starts_with("open:") { let msg = JSONParse(&cmd[5..]).unwrap(); let trade_type = msg["tradeType"].as_i64().unwrap_or(0); Log!("Symbol:", msg["symbol"].as_str().unwrap_or(""), ", Direction:", msg["direction"].as_str().unwrap_or(""), ", Order type:", if trade_type == 0 { "Market order" } else { "Limit order" }, if trade_type == 0 { ", Price: Current market price".to_string() } else { format!(", Price:{}", msg["price"].as_f64().unwrap_or(0.0)) }, ", Amount:", msg["amount"].as_f64().unwrap_or(0.0)); } } LogStatus!(_D(None), "\n", format!("`{}`", tbl)); Sleep(1000); } }c++// Omitted... -
Horizontally merge cells in the table drawn by the
LogStatus()function:javascriptfunction main() { var table = { type: 'table', title: 'Position Operation', cols: ['Column 1', 'Column 2', 'Action'], rows: [ ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'Close'}] ] } var ticker = exchange.GetTicker() // Add a row of data, merge the first and second cells, and output the ticker variable in the merged cell table.rows.push([{body : JSON.stringify(ticker), colspan : 2}, "abc"]) LogStatus('`' + JSON.stringify(table) + '`') }pythonimport json def main(): table = { "type" : "table", "title" : "Position Operation", "cols" : ["Column 1", "Column 2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "Close"}] ] } ticker = exchange.GetTicker() table["rows"].append([{"body": json.dumps(ticker), "colspan": 2}, "abc"]) LogStatus("`" + json.dumps(table) + "`")rustfn main() { let table_tpl = String::from(r#"{"type": "table", "title": "Position Operation", "cols": ["Column 1", "Column 2", "Action"], "rows": ["#) + r#"["abc", "def", {"type": "button", "cmd": "coverAll", "name": "Close"}],"# + r#"__ROW2__"# + r#"]}"#; let ticker = exchange.GetTicker(None).unwrap(); let json_ticker = format!( r#"{{"Buy": {}, "Sell": {}, "High": {}, "Low": {}, "Volume": {}, "Last": {}, "Time": {}}}"#, ticker.Buy, ticker.Sell, ticker.High, ticker.Low, ticker.Volume, ticker.Last, ticker.Time ); // Add a row of data, merge the first and second cells, and output the ticker data in the merged cell // body is a string (corresponding to JS's JSON.stringify(ticker)); its internal quotes must be escaped before being embedded into the JSON let row2 = format!(r#"[{{"body": "{}", "colspan": 2}}, "abc"]"#, json_ticker.replace('"', "\\\"")); let table = table_tpl.replace("__ROW2__", &row2); LogStatus!(format!("`{}`", table)); }c++void main() { json table = R"({ "type" : "table", "title" : "Position Operation", "cols" : ["Column 1", "Column 2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "Close"}] ] })"_json; auto ticker = exchange.GetTicker(); json jsonTicker = R"({"Buy": 0, "Sell": 0, "High": 0, "Low": 0, "Volume": 0, "Last": 0, "Time": 0})"_json; jsonTicker["Buy"] = ticker.Buy; jsonTicker["Sell"] = ticker.Sell; jsonTicker["Last"] = ticker.Last; jsonTicker["Volume"] = ticker.Volume; jsonTicker["Time"] = ticker.Time; jsonTicker["High"] = ticker.High; jsonTicker["Low"] = ticker.Low; json arr = R"([{"body": {}, "colspan": 2}, "abc"])"_json; arr[0]["body"] = jsonTicker; table["rows"].push_back(arr); LogStatus("`" + table.dump() + "`"); } -
Vertically merge cells in a table drawn by the
LogStatus()function:javascriptfunction main() { var table = { type: 'table', title: 'Table Demo', cols: ['Column A', 'Column B', 'Column C'], rows: [ ['A1', 'B1', {'type':'button', 'cmd': 'coverAll', 'name': 'C1'}] ] } var ticker = exchange.GetTicker() var name = exchange.GetName() table.rows.push([{body : "A2 + B2:" + JSON.stringify(ticker), colspan : 2}, "C2"]) table.rows.push([{body : "A3 + A4 + A5:" + name, rowspan : 3}, "B3", "C3"]) // A3 is merged into the first cell of the previous row table.rows.push(["B4", "C4"]) // A2 is merged into the first cell of the previous row table.rows.push(["B5", "C5"]) table.rows.push(["A6", "B6", "C6"]) LogStatus('`' + JSON.stringify(table) + '`') }pythonimport json def main(): table = { "type" : "table", "title" : "Table Demo", "cols" : ["Column A", "Column B", "Column C"], "rows" : [ ["A1", "B1", {"type": "button", "cmd": "coverAll", "name": "C1"}] ] } ticker = exchange.GetTicker() name = exchange.GetName() table["rows"].append([{"body": "A2 + B2:" + json.dumps(ticker), "colspan": 2}, "C2"]) table["rows"].append([{"body": "A3 + A4 + A5:" + name, "rowspan": 3}, "B3", "C3"]) table["rows"].append(["B4", "C4"]) table["rows"].append(["B5", "C5"]) table["rows"].append(["A6", "B6", "C6"]) LogStatus("`" + json.dumps(table) + "`")rustfn main() { // For ease of testing, constructed data is used here to keep the code short and readable let json_ticker = r#"{"High": 0, "Low": 0, "Buy": 0, "Sell": 0, "Last": 0, "Time": 0, "Volume": 0}"#; let name = exchange.GetName(); let mut rows: Vec<String> = Vec::new(); rows.push(String::from(r#"["A1", "B1", {"type": "button", "cmd": "coverAll", "name": "C1"}]"#)); // body is a string, and the quotes inside it must be escaped before they can be embedded in JSON let body = format!("A2 + B2:{}", json_ticker).replace('"', "\\\""); rows.push(format!(r#"[{{"body": "{}", "colspan": 2}}, "C2"]"#, body)); rows.push(format!(r#"[{{"body": "A3 + A4 + A5:{}", "rowspan": 3}}, "B3", "C3"]"#, name)); // A3 is merged into the first cell of the previous row rows.push(String::from(r#"["B4", "C4"]"#)); // A2 is merged into the first cell of the previous row rows.push(String::from(r#"["B5", "C5"]"#)); rows.push(String::from(r#"["A6", "B6", "C6"]"#)); let table = format!(r#"{{"type": "table", "title": "Table Demo", "cols": ["Column A", "Column B", "Column C"], "rows": [{}]}}"#, rows.join(",")); LogStatus!(format!("`{}`", table)); }c++void main() { json table = R"({ "type" : "table", "title" : "Table Demo", "cols" : ["Column A", "Column B", "Column C"], "rows" : [ ["A1", "B1", {"type": "button", "cmd": "coverAll", "name": "C1"}] ] })"_json; // For ease of testing, constructed data is used here to keep the code short and readable json jsonTicker = R"({"High": 0, "Low": 0, "Buy": 0, "Sell": 0, "Last": 0, "Time": 0, "Volume": 0})"_json; auto name = exchange.GetName(); json arr1 = R"([{"body": "", "colspan": 2}, "C2"])"_json; arr1[0]["body"] = "A2 + B2:" + jsonTicker.dump(); json arr2 = R"([{"body": "", "rowspan": 3}, "B3", "C3"])"_json; arr2[0]["body"] = "A3 + A4 + A5:" + name; table["rows"].push_back(arr1); table["rows"].push_back(arr2); table["rows"].push_back(R"(["B4", "C4"])"_json); table["rows"].push_back(R"(["B5", "C5"])"_json); table["rows"].push_back(R"(["A6", "B6", "C6"])"_json); LogStatus("`" + table.dump() + "`"); } -
Display tables with pagination in the status bar:
javascriptfunction main() { var table1 = {type: 'table', title: 'table1', cols: ['Column 1', 'Column 2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]} var table2 = {type: 'table', title: 'table2', cols: ['Column 1', 'Column 2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]} LogStatus('`' + JSON.stringify([table1, table2]) + '`') }pythonimport json def main(): table1 = {"type": "table", "title": "table1", "cols": ["Column 1", "Column 2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]} table2 = {"type": "table", "title": "table2", "cols": ["Column 1", "Column 2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]} LogStatus("`" + json.dumps([table1, table2]) + "`")rustfn main() { let table1 = r#"{"type": "table", "title": "table1", "cols": ["Column 1", "Column 2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]}"#; let table2 = r#"{"type": "table", "title": "table2", "cols": ["Column 1", "Column 2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]}"#; LogStatus!(format!("`[{},{}]`", table1, table2)); }c++void main() { json table1 = R"({"type": "table", "title": "table1", "cols": ["Column 1", "Column 2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]})"_json; json table2 = R"({"type": "table", "title": "table2", "cols": ["Column 1", "Column 2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]})"_json; json arr = R"([])"_json; arr.push_back(table1); arr.push_back(table2); LogStatus("`" + arr.dump() + "`"); } -
In addition to displaying tables with pagination, you can also arrange multiple tables from top to bottom:
javascriptfunction main(){ var tab1 = { type : "table", title : "Table 1", cols : ["1", "2"], rows : [] } var tab2 = { type : "table", title : "Table 2", cols : ["1", "2", "3"], rows : [] } var tab3 = { type : "table", title : "Table 3", cols : ["A", "B", "C"], rows : [] } tab1.rows.push(["jack", "lucy"]) tab2.rows.push(["A", "B", "C"]) tab3.rows.push(["A", "B", "C"]) LogStatus('`' + JSON.stringify(tab1) + '`\n' + '`' + JSON.stringify(tab2) + '`\n' + '`' + JSON.stringify(tab3) + '`') Log("exit") }pythonimport json def main(): tab1 = { "type": "table", "title": "Table 1", "cols": ["1", "2"], "rows": [] } tab2 = { "type": "table", "title": "Table 2", "cols": ["1", "2", "3"], "rows": [] } tab3 = { "type": "table", "title": "Table 3", "cols": ["A", "B", "C"], "rows": [] } tab1["rows"].append(["jack", "lucy"]) tab2["rows"].append(["A", "B", "C"]) tab3["rows"].append(["A", "B", "C"]) LogStatus("`" + json.dumps(tab1) + "`\n" + "`" + json.dumps(tab2) + "`\n" + "`" + json.dumps(tab3) + "`")rustfn main() { // In Rust, write the row data directly into the JSON string let tab1 = r#"{"type": "table", "title": "Table 1", "cols": ["1", "2"], "rows": [["jack", "lucy"]]}"#; let tab2 = r#"{"type": "table", "title": "Table 2", "cols": ["1", "2", "3"], "rows": [["A", "B", "C"]]}"#; let tab3 = r#"{"type": "table", "title": "Table 3", "cols": ["A", "B", "C"], "rows": [["A", "B", "C"]]}"#; LogStatus!(format!("`{}`\n`{}`\n`{}`", tab1, tab2, tab3)); Log!("exit"); }c++void main() { json tab1 = R"({ "type": "table", "title": "Table 1", "cols": ["1", "2"], "rows": [] })"_json; json tab2 = R"({ "type": "table", "title": "Table 2", "cols": ["1", "2", "3"], "rows": [] })"_json; json tab3 = R"({ "type": "table", "title": "Table 3", "cols": ["A", "B", "C"], "rows": [] })"_json; tab1["rows"].push_back(R"(["jack", "lucy"])"_json); tab2["rows"].push_back(R"(["A", "B", "C"])"_json); tab3["rows"].push_back(R"(["A", "B", "C"])"_json); LogStatus("`" + tab1.dump() + "`\n" + "`" + tab2.dump() + "`\n" + "`" + tab3.dump() + "`"); } -
Supports setting horizontal and vertical scroll modes for the status bar table. After setting the
scrollattribute to"auto", when the number of vertical rows in the status bar table exceeds 20, the content will scroll automatically; when the number of horizontal columns exceeds the visible range of the page, horizontal scrolling will be applied. Using thescrollattribute can help mitigate the lag caused by writing large amounts of data to the status bar during live trading.Refer to the following test example:
javascriptfunction main() { var tbl = { type : "table", title : "test scroll", scroll : "auto", cols : ["col 0", "col 1", "col 2", "col 3", "col 4", "col 5", "col 6", "col 7", "col 8", "col 9", "col 10", "col 11", "col 12", "col 13", "col 14", "col 15", "col 16", "col 17", "col 18", "col 19", "col 20"], rows : [] } for (var i = 1 ; i < 100 ; i++) { tbl.rows.push([i, "1," + i, "2," + i, "3," + i, "4," + i, "5," + i, "6," + i, "7," + i, "8," + i, "9," + i, "10," + i, "11," + i, "12," + i, "13," + i, "14," + i, "15," + i, "16," + i, "17," + i, "18," + i, "19," + i, "20," + i]) } LogStatus("`" + JSON.stringify(tbl) + "`") }pythonimport json def main(): tbl = { "type" : "table", "title" : "test scroll", "scroll" : "auto", "cols" : ["col 0", "col 1", "col 2", "col 3", "col 4", "col 5", "col 6", "col 7", "col 8", "col 9", "col 10", "col 11", "col 12", "col 13", "col 14", "col 15", "col 16", "col 17", "col 18", "col 19", "col 20"], "rows" : [] } for index in range(1, 100): i = str(index) tbl["rows"].append([i, "1," + i, "2," + i, "3," + i, "4," + i, "5," + i, "6," + i, "7," + i, "8," + i, "9," + i, "10," + i, "11," + i, "12," + i, "13," + i, "14," + i, "15," + i, "16," + i, "17," + i, "18," + i, "19," + i, "20," + i]) LogStatus("`" + json.dumps(tbl) + "`")rustfn main() { let tbl_tpl = String::from(r#"{"type": "table", "title": "test scroll", "scroll": "auto", "cols": ["#) + r#""col 0", "col 1", "col 2", "col 3", "col 4", "col 5", "col 6", "col 7", "col 8", "col 9", "col 10","# + r#""col 11", "col 12", "col 13", "col 14", "col 15", "col 16", "col 17", "col 18", "col 19", "col 20""# + r#"], "rows": [__ROWS__]}"#; let mut rows: Vec<String> = Vec::new(); for index in 1..100 { let i = index.to_string(); rows.push(format!( r#"[{}, "1,{}", "2,{}", "3,{}", "4,{}", "5,{}", "6,{}", "7,{}", "8,{}", "9,{}", "10,{}", "11,{}", "12,{}", "13,{}", "14,{}", "15,{}", "16,{}", "17,{}", "18,{}", "19,{}", "20,{}"]"#, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i )); } let tbl = tbl_tpl.replace("__ROWS__", &rows.join(",")); LogStatus!(format!("`{}`", tbl)); }c++void main() { json table = R"({ "type" : "table", "title" : "test scroll", "scroll" : "auto", "cols" : ["col 0", "col 1", "col 2", "col 3", "col 4", "col 5", "col 6", "col 7", "col 8", "col 9", "col 10", "col 11", "col 12", "col 13", "col 14", "col 15", "col 16", "col 17", "col 18", "col 19", "col 20"], "rows" : [] })"_json; for (int index = 1; index < 100; ++index) { std::string i = std::to_string(index); table["rows"].push_back({i, "1," + i, "2," + i, "3," + i, "4," + i, "5," + i, "6," + i, "7," + i, "8," + i, "9," + i, "10," + i, "11," + i, "12," + i, "13," + i, "14," + i, "15," + i, "16," + i, "17," + i, "18," + i, "19," + i, "20," + i}); } LogStatus("`" + table.dump() + "`"); }
Arguments
| Name | Type | Required | Description |
msg | string / number / bool / object / array / any (any type supported by the platform) | No | The parameter |
See Also
Remarks
During live trading, the information output by the LogStatus() function is not saved to the live trading database; it only updates the content of the current live trading status bar.
The LogStatus() function supports printing base64-encoded images. The image string starts with ` and ends with `. For example: LogStatus("`data:image/png;base64,AAAA`").
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)
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.
EnableLog
Enable or disable logging of order information.
EnableLog(enable)Examples
javascript
function main() {
EnableLog(false)
}
python
def main():
EnableLog(False)
rust
fn main() {
EnableLog(false);
}
c++
void main() {
EnableLog(false);
}Arguments
| Name | Type | Required | Description |
enable | bool | Yes | When the |
See Also
Chart
Custom chart plotting function.
Chart(options)Examples
-
Multi-chart drawing configuration notes:
extension.layoutproperty
When this property is set to "single", the chart will not be displayed stacked with other charts (i.e., it is not presented as tabbed pages), but is instead tiled separately.extension.heightproperty
This property is used to set the height of the chart. The value can be a numeric type, or it can be set in the form of "300px".extension.colproperty
This property is used to set the width of the chart. The page width is divided into 12 units in total; setting it to 8 means the chart occupies 8 units of width.
javascriptfunction main() { var cfgA = { extension: { layout: 'single', // Not included in grouping, displayed separately; the default is grouping 'group' height: 300, // Specify height }, title: { text: 'Order Book Chart' }, xAxis: { type: 'datetime' }, series: [{ name: 'Bid 1', data: [], }, { name: 'Ask 1', data: [], }] } var cfgB = { title: { text: 'Spread Chart' }, xAxis: { type: 'datetime' }, series: [{ name: 'Spread', type: 'column', data: [], }] } var cfgC = { __isStock: false, title: { text: 'Pie Chart' }, series: [{ type: 'pie', name: 'one', data: [ ["A", 25], ["B", 25], ["C", 25], ["D", 25], ] // After specifying the initial data, there is no need to use the add function to update; you can update the data series simply by modifying the chart configuration directly. }] }; var cfgD = { extension: { layout: 'single', col: 8, // Specify the number of units the width occupies; the total number of units is 12 height: '300px', }, title: { text: 'Order Book Chart' }, xAxis: { type: 'datetime' }, series: [{ name: 'Bid 1', data: [], }, { name: 'Ask 1', data: [], }] } var cfgE = { __isStock: false, extension: { layout: 'single', col: 4, height: '300px', }, title: { text: 'Pie Chart 2' }, series: [{ type: 'pie', name: 'one', data: [ ["A", 25], ["B", 25], ["C", 25], ["D", 25], ] }] }; var chart = Chart([cfgA, cfgB, cfgC, cfgD, cfgE]); chart.reset() // Append a data point to the pie chart; add can only update data points that were added via the add method, built-in data points cannot be updated later chart.add(3, { name: "ZZ", y: Math.random() * 100 }); while (true) { Sleep(1000) var ticker = exchange.GetTicker() if (!ticker) { continue; } var diff = ticker.Sell - ticker.Buy cfgA.subtitle = { text: 'Bid ' + ticker.Buy + ', Ask ' + ticker.Sell, }; cfgB.subtitle = { text: 'Spread ' + diff, }; chart.add([0, [new Date().getTime(), ticker.Buy]]); chart.add([1, [new Date().getTime(), ticker.Sell]]); // Equivalent to updating the first data series of the second chart chart.add([2, [new Date().getTime(), diff]]); chart.add(4, [new Date().getTime(), ticker.Buy]); chart.add(5, [new Date().getTime(), ticker.Buy]); cfgC.series[0].data[0][1] = Math.random() * 100; cfgE.series[0].data[0][1] = Math.random() * 100; // update is actually equivalent to resetting the chart configuration chart.update([cfgA, cfgB, cfgC, cfgD, cfgE]); } }pythonimport random import time def main(): cfgA = { "extension" : { "layout" : "single", "height" : 300, "col" : 8 }, "title" : { "text" : "Order Book Chart" }, "xAxis" : { "type" : "datetime" }, "series" : [{ "name" : "Bid 1", "data" : [] }, { "name" : "Ask 1", "data" : [] }] } cfgB = { "title" : { "text" : "Spread Chart" }, "xAxis" : { "type" : "datetime", }, "series" : [{ "name" : "Spread", "type" : "column", "data" : [] }] } cfgC = { "__isStock" : False, "title" : { "text" : "Pie Chart" }, "series" : [{ "type" : "pie", "name" : "one", "data" : [ ["A", 25], ["B", 25], ["C", 25], ["D", 25], ] }] } cfgD = { "extension" : { "layout" : "single", "col" : 8, "height" : "300px" }, "title" : { "text" : "Order Book Chart" }, "series" : [{ "name" : "Bid 1", "data" : [] }, { "name" : "Ask 1", "data" : [] }] } cfgE = { "__isStock" : False, "extension" : { "layout" : "single", "col" : 4, "height" : "300px" }, "title" : { "text" : "Pie Chart 2" }, "series" : [{ "type" : "pie", "name" : "one", "data" : [ ["A", 25], ["B", 25], ["C", 25], ["D", 25] ] }] } chart = Chart([cfgA, cfgB, cfgC, cfgD, cfgE]) chart.reset() chart.add(3, { "name" : "ZZ", "y" : random.random() * 100 }) while True: Sleep(1000) ticker = exchange.GetTicker() if not ticker : continue diff = ticker["Sell"] - ticker["Buy"] cfgA["subtitle"] = { "text" : "Bid " + str(ticker["Buy"]) + " Ask " + str(ticker["Sell"]) } cfgB["subtitle"] = { "text" : "Spread " + str(diff) } chart.add(0, [time.time() * 1000, ticker["Buy"]]) chart.add(1, [time.time() * 1000, ticker["Sell"]]) chart.add(2, [time.time() * 1000, diff]) chart.add(4, [time.time() * 1000, ticker["Buy"]]) chart.add(5, [time.time() * 1000, ticker["Buy"]]) cfgC["series"][0]["data"][0][1] = random.random() * 100 cfgE["series"][0]["data"][0][1] = random.random() * 100rustfn main() { // In Rust, the chart configuration is a JSON string; variable parts are represented with placeholders, which are replaced when updating to rebuild the configuration let cfg_a_tpl = r#"{ "extension": { "layout": "single", "height": 300 }, "title": {"text": "Order Book Chart"}, "subtitle": {"text": "__SUBTITLE__"}, "xAxis": {"type": "datetime"}, "series": [{"name": "Bid 1", "data": []}, {"name": "Ask 1", "data": []}] }"#; let cfg_b_tpl = r#"{ "title": {"text": "Spread Chart"}, "subtitle": {"text": "__SUBTITLE__"}, "xAxis": {"type": "datetime"}, "series": [{"name": "Spread", "type": "column", "data": []}] }"#; let cfg_c_tpl = r#"{ "__isStock": false, "title": {"text": "Pie Chart"}, "series": [{ "type": "pie", "name": "one", "data": [["A", __Y__], ["B", 25], ["C", 25], ["D", 25]] }] }"#; let cfg_d = r#"{ "extension": { "layout": "single", "col": 8, "height": "300px" }, "title": {"text": "Order Book Chart"}, "xAxis": {"type": "datetime"}, "series": [{"name": "Bid 1", "data": []}, {"name": "Ask 1", "data": []}] }"#; let cfg_e_tpl = r#"{ "__isStock": false, "extension": { "layout": "single", "col": 4, "height": "300px" }, "title": {"text": "Pie Chart 2"}, "series": [{ "type": "pie", "name": "one", "data": [["A", __Y__], ["B", 25], ["C", 25], ["D", 25]] }] }"#; let cfg_a = cfg_a_tpl.replace("__SUBTITLE__", ""); let cfg_b = cfg_b_tpl.replace("__SUBTITLE__", ""); let cfg_c = cfg_c_tpl.replace("__Y__", "25"); let cfg_e = cfg_e_tpl.replace("__Y__", "25"); let chart = Chart::new(&format!("[{},{},{},{},{}]", cfg_a, cfg_b, cfg_c, cfg_d, cfg_e)); chart.reset(0); // Append a data point to the pie chart; add can only update data points that were added via the add method, built-in data points cannot be updated later let y = (UnixNano() % 100) as f64; // Use the timestamp to simulate a random number chart.add(3, &format!(r#"{{"name": "ZZ", "y": {}}}"#, y), -1); loop { Sleep(1000); let ticker = match exchange.GetTicker(None) { Ok(t) => t, Err(_) => continue, }; let diff = ticker.Sell - ticker.Buy; let cfg_a = cfg_a_tpl.replace("__SUBTITLE__", &format!("Bid {}, Ask {}", ticker.Buy, ticker.Sell)); let cfg_b = cfg_b_tpl.replace("__SUBTITLE__", &format!("Spread {}", diff)); let now = Unix() * 1000; chart.add(0, &format!("[{}, {}]", now, ticker.Buy), -1); chart.add(1, &format!("[{}, {}]", now, ticker.Sell), -1); // Equivalent to updating the first data series of the second chart chart.add(2, &format!("[{}, {}]", now, diff), -1); chart.add(4, &format!("[{}, {}]", now, ticker.Buy), -1); chart.add(5, &format!("[{}, {}]", now, ticker.Buy), -1); let cfg_c = cfg_c_tpl.replace("__Y__", &format!("{}", (UnixNano() % 100) as f64)); let cfg_e = cfg_e_tpl.replace("__Y__", &format!("{}", (UnixNano() % 100) as f64)); // update is actually equivalent to resetting the chart configuration chart.update(&format!("[{},{},{},{},{}]", cfg_a, cfg_b, cfg_c, cfg_d, cfg_e)); } }c++void main() { json cfgA = R"({ "extension" : { "layout" : "single", "height" : 300, "col" : 8 }, "title" : { "text" : "Order Book Chart" }, "xAxis" : { "type" : "datetime" }, "series" : [{ "name" : "Bid 1", "data" : [] }, { "name" : "Ask 1", "data" : [] }] })"_json; json cfgB = R"({ "title" : { "text" : "Spread Chart" }, "xAxis" : { "type" : "datetime" }, "series" : [{ "name" : "Spread", "type" : "column", "data" : [] }] })"_json; json cfgC = R"({ "__isStock" : false, "title" : { "text" : "Pie Chart" }, "series" : [{ "type" : "pie", "name" : "one", "data" : [ ["A", 25], ["B", 25], ["C", 25], ["D", 25] ] }] })"_json; json cfgD = R"({ "extension" : { "layout" : "single", "col" : 8, "height" : "300px" }, "title" : { "text" : "Order Book Chart" }, "series" : [{ "name" : "Bid 1", "data" : [] }, { "name" : "Ask 1", "data" : [] }] })"_json; json cfgE = R"({ "__isStock" : false, "extension" : { "layout" : "single", "col" : 4, "height" : "300px" }, "title" : { "text" : "Pie Chart 2" }, "series" : [{ "type" : "pie", "name" : "one", "data" : [ ["A", 25], ["B", 25], ["C", 25], ["D", 25] ] }] })"_json; auto chart = Chart({cfgA, cfgB, cfgC, cfgD, cfgE}); chart.reset(); json zz = R"({ "name" : "ZZ", "y" : 0 })"_json; zz["y"] = rand() % 100; chart.add(3, zz); while(true) { Sleep(1000); auto ticker = exchange.GetTicker(); if(!ticker.Valid) { continue; } auto diff = ticker.Sell - ticker.Buy; json cfgASubTitle = R"({"text" : ""})"_json; cfgASubTitle["text"] = str_format("Bid %f , Ask %f", ticker.Buy, ticker.Sell); cfgA["subtitle"] = cfgASubTitle; json cfgBSubTitle = R"({"text" : ""})"_json; cfgBSubTitle["text"] = str_format("Spread %f", diff); cfgB["subtitle"] = cfgBSubTitle; chart.add(0, {Unix() * 1000, ticker.Buy}); chart.add(1, {Unix() * 1000, ticker.Sell}); chart.add(2, {Unix() * 1000, diff}); chart.add(4, {Unix() * 1000, ticker.Buy}); chart.add(5, {Unix() * 1000, ticker.Buy}); cfgC["series"][0]["data"][0][1] = rand() % 100; cfgE["series"][0]["data"][0][1] = rand() % 100; chart.update({cfgA, cfgB, cfgC, cfgD, cfgE}); } } -
A simple charting example:
javascript// In JavaScript, chart is an object; before calling the Chart function, we need to declare an object variable chart used to configure the chart var chart = { // This field marks whether the chart is an ordinary chart; interested readers can change it to false and run it to see the effect __isStock: true, // Tooltip tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'}, // Title title : { text : 'Spread Analysis Chart'}, // Range selector rangeSelector: { buttons: [{type: 'hour',count: 1, text: '1h'}, {type: 'hour',count: 3, text: '3h'}, {type: 'hour', count: 8, text: '8h'}, {type: 'all',text: 'All'}], selected: 0, inputEnabled: false }, // Horizontal axis (i.e., the x-axis); the currently set type is: datetime xAxis: { type: 'datetime'}, // Vertical axis (i.e., the y-axis); by default the values are automatically adjusted according to the data size yAxis : { // Title title: {text: 'Spread'}, // Whether to enable the right-side vertical axis opposite: false }, // Data series; this property holds each data series (line charts, candlestick charts, labels, etc.) series : [ // Index 0; the data array stores the data for the series at this index {name : "line1", id : "Line 1,buy1Price", data : []}, // Index 1; dashStyle: 'shortdash' is set, i.e., it is set as a dashed line {name : "line2", id : "Line 2,lastPrice", dashStyle : 'shortdash', data : []} ] } function main(){ // Call the Chart function to initialize the chart var ObjChart = Chart(chart) // Clear ObjChart.reset() while(true){ // Get the timestamp of this poll (i.e., a millisecond-level timestamp), used to determine the X-axis position written to the chart var nowTime = new Date().getTime() // Get the ticker data var ticker = _C(exchange.GetTicker) // Get the best bid price from the return value of the ticker data var buy1Price = ticker.Buy // Get the last traded price; to prevent the two lines from overlapping, add 1 to it here var lastPrice = ticker.Last + 1 // Pass the timestamp as the X value and the best bid price as the Y value into the data series at index 0 ObjChart.add(0, [nowTime, buy1Price]) // Same as above ObjChart.add(1, [nowTime, lastPrice]) Sleep(2000) } }pythonimport time chart = { "__isStock" : True, "tooltip" : {"xDateFormat" : "%Y-%m-%d %H:%M:%S, %A"}, "title" : {"text" : "Spread Analysis Chart"}, "rangeSelector" : { "buttons" : [{"type": "count", "count": 1, "text": "1h"}, {"type": "hour", "count": 3, "text": "3h"}, {"type": "hour", "count": 8, "text": "8h"}, {"type": "all", "text": "All"}], "selected": 0, "inputEnabled": False }, "xAxis": {"type": "datetime"}, "yAxis": { "title": {"text": "Spread"}, "opposite": False }, "series": [{ "name": "line1", "id": "Line 1,buy1Price", "data": [] }, { "name": "line2", "id": "Line 2,lastPrice", "dashStyle": "shortdash", "data": [] }] } def main(): ObjChart = Chart(chart) ObjChart.reset() while True: nowTime = time.time() * 1000 ticker = exchange.GetTicker() buy1Price = ticker["Buy"] lastPrice = ticker["Last"] + 1 ObjChart.add(0, [nowTime, buy1Price]) ObjChart.add(1, [nowTime, lastPrice]) Sleep(2000)rustfn main() { // In Rust, the chart configuration is a JSON string; before calling the Chart::new function, define the chart configuration first let chart = r#"{ "__isStock": true, "tooltip": {"xDateFormat": "%Y-%m-%d %H:%M:%S, %A"}, "title": {"text": "Spread Analysis Chart"}, "rangeSelector": { "buttons": [{"type": "hour", "count": 1, "text": "1h"}, {"type": "hour", "count": 3, "text": "3h"}, {"type": "hour", "count": 8, "text": "8h"}, {"type": "all", "text": "All"}], "selected": 0, "inputEnabled": false }, "xAxis": {"type": "datetime"}, "yAxis": { "title": {"text": "Spread"}, "opposite": false }, "series": [ {"name": "line1", "id": "Line 1,buy1Price", "data": []}, {"name": "line2", "id": "Line 2,lastPrice", "dashStyle": "shortdash", "data": []} ] }"#; // Call the Chart::new function to initialize the chart let obj_chart = Chart::new(chart); // Clear obj_chart.reset(0); loop { // Get the timestamp of this poll (i.e., a millisecond-level timestamp), used to determine the X-axis position written to the chart let now_time = Unix() * 1000; // Get the ticker data let ticker = _C!(exchange.GetTicker(None)); // Get the best bid price from the return value of the ticker data let buy1_price = ticker.Buy; // Get the last traded price; to prevent the two lines from overlapping, add 1 to it here let last_price = ticker.Last + 1.0; // Pass the timestamp as the X value and the best bid price as the Y value into the data series at index 0 obj_chart.add(0, &format!("[{}, {}]", now_time, buy1_price), -1); // Same as above obj_chart.add(1, &format!("[{}, {}]", now_time, last_price), -1); Sleep(2000); } }c++void main() { // When writing a strategy in C++, try not to declare global variables of non-primitive types, so the chart configuration object is declared inside the main function json chart = R"({ "__isStock" : true, "tooltip" : {"xDateFormat" : "%Y-%m-%d %H:%M:%S, %A"}, "title" : {"text" : "Spread Analysis Chart"}, "rangeSelector" : { "buttons" : [{"type": "count", "count": 1, "text": "1h"}, {"type": "hour", "count": 3, "text": "3h"}, {"type": "hour", "count": 8, "text": "8h"}, {"type": "all", "text": "All"}], "selected": 0, "inputEnabled": false }, "xAxis": {"type": "datetime"}, "yAxis": { "title": {"text": "Spread"}, "opposite": false }, "series": [{ "name": "line1", "id": "Line 1,buy1Price", "data": [] }, { "name": "line2", "id": "Line 2,lastPrice", "dashStyle": "shortdash", "data": [] }] })"_json; auto ObjChart = Chart(chart); ObjChart.reset(); while(true) { auto nowTime = Unix() * 1000; auto ticker = exchange.GetTicker(); auto buy1Price = ticker.Buy; auto lastPrice = ticker.Last + 1.0; ObjChart.add(0, {nowTime, buy1Price}); ObjChart.add(1, {nowTime, lastPrice}); Sleep(2000); } } -
Example of drawing trigonometric function curves:
javascript// Configuration object used to initialize the chart var chart = { // Chart title title: {text: "Line value triggers plotLines value"}, // Y-axis related settings yAxis: { // A horizontal line perpendicular to the Y-axis, used as a trigger line; this is an array of structs, and multiple trigger lines can be set plotLines: [{ // The value of the trigger line; the line will be displayed at the corresponding numerical position value: 0, // Set the color of the trigger line color: 'red', // Line width width: 2, // The displayed label label: { // Label text text: 'Trigger Value', // Center-align the label align: 'center' } }] }, // X-axis related settings; here the type is set to a datetime axis xAxis: {type: "datetime"}, series: [ {name: "sin", type: "spline", data: []}, // Data series; multiple can be set and controlled via array indices {name: "cos", type: "spline", data: []} ] } function main(){ // Pi var pi = 3.1415926535897 // Variable used to record the timestamp var time = 0 // Angle var angle = 0 // The y-coordinate value, used to receive the sine or cosine value var y = 0 // Call the API interface to initialize the chart using the chart object var objChart = Chart(chart) // Clear the chart during initialization objChart.reset() // Set the value of the trigger line to 1 chart.yAxis.plotLines[0].value = 1 // Loop while(true){ // Get the timestamp of the current moment time = new Date().getTime() // Every 500ms, increase the angle by 5 degrees and calculate the sine value y = Math.sin(angle * 2 * pi / 360) // Write the calculated y value into the data series at the corresponding index in the chart; the first parameter of the add function is the specified data series index objChart.add(0, [time, y]) // Calculate the cosine value y = Math.cos(angle * 2 * pi / 360) objChart.add(1, [time, y]) // Increase by 5 degrees angle += 5 // Pause for 5 seconds to avoid plotting too frequently and data growing too fast Sleep(5000) } }pythonimport math import time chart = { "title": {"text": "Line value triggers plotLines value"}, "yAxis": { "plotLines": [{ "value": 0, "color": "red", "width": 2, "label": { "text": "Trigger Value", "align": "center" } }] }, "xAxis": {"type": "datetime"}, "series": [{"name": "sin", "type": "spline", "data": []}, {"name": "cos", "type": "spline", "data": []}] } def main(): pi = 3.1415926535897 ts = 0 angle = 0 y = 0 objChart = Chart(chart) objChart.reset() chart["yAxis"]["plotLines"][0]["value"] = 1 while True: ts = time.time() * 1000 y = math.sin(angle * 2 * pi / 360) objChart.add(0, [ts, y]) y = math.cos(angle * 2 * pi / 360) objChart.add(1, [ts, y]) angle += 5 Sleep(5000)rustfn main() { // JSON configuration string used to initialize the chart; the trigger line value is set directly to 1 in the configuration let chart = r#"{ "title": {"text": "Line value triggers plotLines value"}, "yAxis": { "plotLines": [{ "value": 1, "color": "red", "width": 2, "label": { "text": "Trigger Value", "align": "center" } }] }, "xAxis": {"type": "datetime"}, "series": [{"name": "sin", "type": "spline", "data": []}, {"name": "cos", "type": "spline", "data": []}] }"#; // Pi let pi = 3.1415926535897_f64; // Angle let mut angle = 0.0_f64; // Call the API interface to initialize the chart using the chart configuration let obj_chart = Chart::new(chart); // Clear the chart during initialization obj_chart.reset(0); // Loop loop { // Get the millisecond timestamp of the current moment let ts = Unix() * 1000; // Increase the angle by 5 degrees and calculate the sine value let mut y = (angle * 2.0 * pi / 360.0).sin(); // Write the calculated y value into the data series at the corresponding index in the chart; the first parameter of the add function is the specified data series index obj_chart.add(0, &format!("[{}, {}]", ts, y), -1); // Calculate the cosine value y = (angle * 2.0 * pi / 360.0).cos(); obj_chart.add(1, &format!("[{}, {}]", ts, y), -1); // Increase by 5 degrees angle += 5.0; // Pause for 5 seconds to avoid plotting too frequently and data growing too fast Sleep(5000); } }c++void main() { json chart = R"({ "title": {"text": "Line value triggers plotLines value"}, "yAxis": { "plotLines": [{ "value": 0, "color": "red", "width": 2, "label": { "text": "Trigger Value", "align": "center" } }] }, "xAxis": {"type": "datetime"}, "series": [{"name": "sin", "type": "spline", "data": []}, {"name": "cos", "type": "spline", "data": []}] })"_json; auto pi = 3.1415926535897; auto ts = 0; auto angle = 0.0; auto y = 0.0; auto objChart = Chart(chart); objChart.reset(); chart["yAxis"]["plotLines"][0]["value"] = 1; while(true) { ts = Unix() * 1000; y = sin(angle * 2 * pi / 360); objChart.add(0, {ts, y}); y = cos(angle * 2 * pi / 360); objChart.add(1, {ts, y}); angle += 5; Sleep(5000); } } -
A complex example using a mixed chart:
javascript/*backtest start: 2020-03-11 00:00:00 end: 2020-04-09 23:59:00 period: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ var chartCfg = { subtitle: { text: "subtitle", }, yAxis: [{ height: "40%", lineWidth: 2, title: { text: 'PnL', }, tickPixelInterval: 20, minorGridLineWidth: 1, minorTickWidth: 0, opposite: true, labels: { align: "right", x: -3, } }, { title: { text: 'Profit', }, top: "42%", height: "18%", offset: 0, lineWidth: 2 }, { title: { text: 'Vol', }, top: '62%', height: '18%', offset: 0, lineWidth: 2 }, { title: { text: 'Asset', }, top: '82%', height: '18%', offset: 0, lineWidth: 2 }], series: [{ name: 'PnL', data: [], id: 'primary', tooltip: { xDateFormat: '%Y-%m-%d %H:%M:%S' }, yAxis: 0 }, { type: 'column', lineWidth: 2, name: 'Profit', data: [], yAxis: 1, }, { type: 'column', name: 'Trade', data: [], yAxis: 2 }, { type: 'area', step: true, lineWidth: 0, name: 'Long', data: [], yAxis: 2 }, { type: 'area', step: true, lineWidth: 0, name: 'Short', data: [], yAxis: 2 }, { type: 'line', step: true, color: '#5b4b00', name: 'Asset', data: [], yAxis: 3 }, { type: 'pie', innerSize: '70%', name: 'Random', data: [], center: ['3%', '6%'], size: '15%', dataLabels: { enabled: false }, startAngle: -90, endAngle: 90, }], }; function main() { let c = Chart(chartCfg); let preTicker = null; while (true) { let t = exchange.GetTicker(); c.add(0, [t.Time, t.Last]); // PnL c.add(1, [t.Time, preTicker ? t.Last - preTicker.Last : 0]); // profit let r = Math.random(); var pos = parseInt(t.Time/86400); c.add(2, [t.Time, pos/2]); // Vol c.add(3, [t.Time, r > 0.8 ? pos : null]); // Long c.add(4, [t.Time, r < 0.8 ? -pos : null]); // Short c.add(5, [t.Time, Math.random() * 100]); // Asset // update pie chartCfg.series[chartCfg.series.length-1].data = [ ["A", Math.random()*100], ["B", Math.random()*100], ]; c.update(chartCfg) preTicker = t; } }python'''backtest start: 2020-03-11 00:00:00 end: 2020-04-09 23:59:00 period: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] ''' import random chartCfg = { "subtitle": { "text": "subtitle" }, "yAxis": [{ "height": "40%", "lineWidth": 2, "title": { "text": 'PnL' }, "tickPixelInterval": 20, "minorGridLineWidth": 1, "minorTickWidth": 0, "opposite": True, "labels": { "align": "right", "x": -3 } }, { "title": { "text": 'Profit' }, "top": "42%", "height": "18%", "offset": 0, "lineWidth": 2 }, { "title": { "text": 'Vol' }, "top": '62%', "height": '18%', "offset": 0, "lineWidth": 2 }, { "title": { "text": 'Asset' }, "top": '82%', "height": '18%', "offset": 0, "lineWidth": 2 }], "series": [{ "name": 'PnL', "data": [], "id": 'primary', "tooltip": { "xDateFormat": '%Y-%m-%d %H:%M:%S' }, "yAxis": 0 }, { "type": 'column', "lineWidth": 2, "name": 'Profit', "data": [], "yAxis": 1 }, { "type": 'column', "name": 'Trade', "data": [], "yAxis": 2 }, { "type": 'area', "step": True, "lineWidth": 0, "name": 'Long', "data": [], "yAxis": 2 }, { "type": 'area', "step": True, "lineWidth": 0, "name": 'Short', "data": [], "yAxis": 2 }, { "type": 'line', "step": True, "color": '#5b4b00', "name": 'Asset', "data": [], "yAxis": 3 }, { "type": 'pie', "innerSize": '70%', "name": 'Random', "data": [], "center": ['3%', '6%'], "size": '15%', "dataLabels": { "enabled": False }, "startAngle": -90, "endAngle": 90 }] } def main(): c = Chart(chartCfg) preTicker = None while True: t = exchange.GetTicker() c.add(0, [t["Time"], t["Last"]]) profit = t["Last"] - preTicker["Last"] if preTicker else 0 c.add(1, [t["Time"], profit]) r = random.random() pos = t["Time"] / 86400 c.add(2, [t["Time"], pos / 2]) long = pos if r > 0.8 else None c.add(3, [t["Time"], long]) short = -pos if r < 0.8 else None c.add(4, [t["Time"], short]) c.add(5, [t["Time"], random.random() * 100]) # update pie chartCfg["series"][len(chartCfg["series"]) - 1]["data"] = [ ["A", random.random() * 100], ["B", random.random() * 100] ] c.update(chartCfg) preTicker = trust/*backtest start: 2020-03-11 00:00:00 end: 2020-04-09 23:59:00 period: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ fn main() { // In Rust, the chart configuration is represented as a JSON string; the pie chart data is marked with the placeholder __PIE_DATA__, which is replaced to rebuild the configuration on update let chart_cfg_tpl = r##"{ "subtitle": {"text": "subtitle"}, "yAxis": [{ "height": "40%", "lineWidth": 2, "title": {"text": "PnL"}, "tickPixelInterval": 20, "minorGridLineWidth": 1, "minorTickWidth": 0, "opposite": true, "labels": {"align": "right", "x": -3} }, { "title": {"text": "Profit"}, "top": "42%", "height": "18%", "offset": 0, "lineWidth": 2 }, { "title": {"text": "Vol"}, "top": "62%", "height": "18%", "offset": 0, "lineWidth": 2 }, { "title": {"text": "Asset"}, "top": "82%", "height": "18%", "offset": 0, "lineWidth": 2 }], "series": [{ "name": "PnL", "data": [], "id": "primary", "tooltip": {"xDateFormat": "%Y-%m-%d %H:%M:%S"}, "yAxis": 0 }, { "type": "column", "lineWidth": 2, "name": "Profit", "data": [], "yAxis": 1 }, { "type": "column", "name": "Trade", "data": [], "yAxis": 2 }, { "type": "area", "step": true, "lineWidth": 0, "name": "Long", "data": [], "yAxis": 2 }, { "type": "area", "step": true, "lineWidth": 0, "name": "Short", "data": [], "yAxis": 2 }, { "type": "line", "step": true, "color": "#5b4b00", "name": "Asset", "data": [], "yAxis": 3 }, { "type": "pie", "innerSize": "70%", "name": "Random", "data": __PIE_DATA__, "center": ["3%", "6%"], "size": "15%", "dataLabels": {"enabled": false}, "startAngle": -90, "endAngle": 90 }] }"##; let c = Chart::new(&chart_cfg_tpl.replace("__PIE_DATA__", "[]")); let mut pre_ticker: Option<Ticker> = None; loop { let t = exchange.GetTicker(None).unwrap(); c.add(0, &format!("[{}, {}]", t.Time, t.Last), -1); // PnL let profit = if let Some(p) = &pre_ticker { t.Last - p.Last } else { 0.0 }; c.add(1, &format!("[{}, {}]", t.Time, profit), -1); // profit let r = (UnixNano() % 100) as f64 / 100.0; // use the timestamp to simulate a random number let pos = (t.Time / 86400) as f64; c.add(2, &format!("[{}, {}]", t.Time, pos / 2.0), -1); // Vol c.add(3, &format!("[{}, {}]", t.Time, if r > 0.8 { pos.to_string() } else { "null".to_string() }), -1); // Long c.add(4, &format!("[{}, {}]", t.Time, if r < 0.8 { (-pos).to_string() } else { "null".to_string() }), -1); // Short c.add(5, &format!("[{}, {}]", t.Time, (UnixNano() % 10000) as f64 / 100.0), -1); // Asset // update pie let pie = format!(r#"[["A", {}], ["B", {}]]"#, (UnixNano() % 100) as f64, (UnixNano() % 100) as f64); c.update(&chart_cfg_tpl.replace("__PIE_DATA__", &pie)); pre_ticker = Some(t); } }c++/*backtest start: 2020-03-11 00:00:00 end: 2020-04-09 23:59:00 period: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ void main() { json chartCfg = R"({ "subtitle": { "text": "subtitle" }, "yAxis": [{ "height": "40%", "lineWidth": 2, "title": { "text": "PnL" }, "tickPixelInterval": 20, "minorGridLineWidth": 1, "minorTickWidth": 0, "opposite": true, "labels": { "align": "right", "x": -3 } }, { "title": { "text": "Profit" }, "top": "42%", "height": "18%", "offset": 0, "lineWidth": 2 }, { "title": { "text": "Vol" }, "top": "62%", "height": "18%", "offset": 0, "lineWidth": 2 }, { "title": { "text": "Asset" }, "top": "82%", "height": "18%", "offset": 0, "lineWidth": 2 }], "series": [{ "name": "PnL", "data": [], "id": "primary", "tooltip": { "xDateFormat": "%Y-%m-%d %H:%M:%S" }, "yAxis": 0 }, { "type": "column", "lineWidth": 2, "name": "Profit", "data": [], "yAxis": 1 }, { "type": "column", "name": "Trade", "data": [], "yAxis": 2 }, { "type": "area", "step": true, "lineWidth": 0, "name": "Long", "data": [], "yAxis": 2 }, { "type": "area", "step": true, "lineWidth": 0, "name": "Short", "data": [], "yAxis": 2 }, { "type": "line", "step": true, "color": "#5b4b00", "name": "Asset", "data": [], "yAxis": 3 }, { "type": "pie", "innerSize": "70%", "name": "Random", "data": [], "center": ["3%", "6%"], "size": "15%", "dataLabels": { "enabled": false }, "startAngle": -90, "endAngle": 90 }] })"_json; Chart c = Chart(chartCfg); Ticker preTicker; while(true) { auto t = exchange.GetTicker(); c.add(0, {t.Time, t.Last}); auto profit = preTicker.Valid ? t.Last - preTicker.Last : 0; c.add(1, {t.Time, profit}); auto r = rand() % 100; auto pos = t.Time / 86400.0; c.add(2, {t.Time, pos / 2.0}); auto longPos = r > 0.8 ? pos : NULL; c.add(3, {t.Time, longPos}); auto shortPos = r < 0.8 ? -pos : NULL; c.add(4, {t.Time, shortPos}); c.add(5, {t.Time, rand() % 100}); // update pie json pie = R"([["A", 0], ["B", 0]])"_json; pie[0][1] = rand() % 100; pie[1][1] = rand() % 100; chartCfg["series"][chartCfg["series"].size() - 1]["data"] = pie; c.update(chartCfg); preTicker = t; } } -
The
pietype chart does not have a time axis, so you need to update the chart configuration directly when updating the data. For example, in the code of the example above, after updating the data, simply callc.update(chartCfg)to refresh the chart, as shown below:javascript// update pie chartCfg.series[chartCfg.series.length-1].data = [ ["A", Math.random()*100], ["B", Math.random()*100], ]; c.update(chartCfg)python# update pie chartCfg["series"][len(chartCfg["series"]) - 1]["data"] = [ ["A", random.random() * 100], ["B", random.random() * 100] ] c.update(chartCfg)rust// update pie // In Rust the chart configuration is a JSON string; rebuild the configuration containing the new data and then call update to refresh the chart let pie = format!(r#"[["A", {}], ["B", {}]]"#, (UnixNano() % 100) as f64, (UnixNano() % 100) as f64); c.update(&chart_cfg_tpl.replace("__PIE_DATA__", &pie));c++// update pie json pie = R"([["A", 0], ["B", 0]])"_json; pie[0][1] = rand() % 100; pie[1][1] = rand() % 100; chartCfg["series"][chartCfg["series"].size() - 1]["data"] = pie; c.update(chartCfg);
Returns
| Type | Description |
object | Chart object. |
Arguments
| Name | Type | Required | Description |
options | object / object array | Yes | The |
See Also
Remarks
The Chart() function returns a chart object, which contains 4 methods: add(), reset(), update(), del().
-
update()method:
Theupdate()method is used to update the chart's configuration information. Its parameter is a Chart chart configuration object (JSON).
-
del()method:
Thedel()method deletes the data series at the specified index according to the passed series parameter.
-
add()method:
Theadd()method is used to write data into the chart. Its parameters are, in order:
series: used to set the index of the data series, an integer.data: used to set the specific data to be written, an array.index(optional): used to set the data index, an integer, specifying the exact index position of the data to be modified. Negative numbers are supported; setting it to-1indicates the last data point of the data set.
For example, when drawing a line, to modify the data of the last point on the line:chart.add(0, [1574993606000, 13.5], -1), i.e. change the data of the last point in the chart'sseries[0].data. When theindexparameter is not set, it means appending data to the end of the current data series (series).
-
reset()method:
Thereset()method is used to clear the chart data. It can take one parameterremain, used to specify the number of data entries to retain. When theremainparameter is not passed, it means clearing all data.
KLineChart
This function is used to perform custom drawing while a strategy is running, using a drawing approach similar to the Pine language.
KLineChart(options)Examples
-
If you need to draw on the strategy's custom chart area, you must first create a chart control object, which can be created using the
KLineChart()function. The argument of theKLineChart()function is a chart configuration structure. The chart configuration structure used in the reference code is very simple:{overlay: true}.This chart configuration structure only sets the drawing content to be output on the main chart. If
overlayis set to a falsy value (for examplefalse), then all the chart content will be output on the sub-chart; if you need to specify that a certain drawing function draws on the main chart, you can also specify the argumentoverlayas a truthy value (for exampletrue) in the specific function call.javascriptfunction main() { // Call the KLineChart function to create the chart control object c let c = KLineChart({ overlay: true }) // Test with a spot exchange object to obtain K-line data. If testing with a futures exchange object, you need to set the contract first let bars = exchange.GetRecords() if (!bars) { return } // Iterate over the K-line data to perform drawing operations. Each drawing operation must start with a ```c.begin(bar)``` function call and end with a ```c.close(bar)``` function call. bars.forEach(function(bar, index) { c.begin(bar) c.barcolor(bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.2)') if (bar.Close > bar.Open) { c.bgcolor('rgba(0, 255, 0, 0.5)') } let h = c.plot(bar.High, 'high') let l = c.plot(bar.Low, 'low') c.fill(h, l, { color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)' }) c.hline(bar.High) c.plotarrow(bar.Close - bar.Open) c.plotshape(bar.Low, { style: 'diamond' }) c.plotchar(bar.Close, { char: 'X' }) c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9) if (bar.Close > bar.Open) { // long/short/closelong/closeshort c.signal("long", bar.High, 1.5) } else if (bar.Close < bar.Open) { c.signal("closelong", bar.Low, 1.5) } c.close(bar) }) }pythondef main(): # Call the KLineChart function to create the chart control object c c = KLineChart({ "overlay": True }) # Test with a spot exchange object to obtain K-line data. If testing with a futures exchange object, you need to set the contract first bars = exchange.GetRecords() if not bars: return for bar in bars: c.begin(bar) c.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)') if bar.Close > bar.Open: c.bgcolor('rgba(0, 255, 0, 0.5)') h = c.plot(bar.High, 'high') l = c.plot(bar.Low, 'low') c.fill(h, l, 'rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(255, 0, 0, 0.2)') c.hline(bar.High) c.plotarrow(bar.Close - bar.Open) c.plotshape(bar.Low, style = 'diamond') c.plotchar(bar.Close, char = 'X') c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9) if bar.Close > bar.Open: # long/short/closelong/closeshort c.signal("long", bar.High, 1.5) elif bar.Close < bar.Open: c.signal("closelong", bar.Low, 1.5) c.close(bar)rustfn main() { // Call KLineChart::new to create the chart control object c let mut c = KLineChart::new(r#"{"overlay": true}"#); // Test with a spot exchange object to obtain K-line data. If testing with a futures exchange object, you need to set the contract first let bars = exchange.GetRecords(None, None, None).unwrap(); // Iterate over the K-line data to perform drawing operations. Each drawing operation must start with a c.begin(bar) function call and end with a c.close() function call. for bar in &bars { c.begin(bar); c.barcolor(if bar.Close > bar.Open { "rgba(255, 0, 0, 0.2)" } else { "rgba(0, 0, 0, 0.2)" }, "{}"); if bar.Close > bar.Open { c.bgcolor("rgba(0, 255, 0, 0.5)", "{}"); } let h = c.plot(bar.High, r#"{"title": "high"}"#); let l = c.plot(bar.Low, r#"{"title": "low"}"#); c.fill(h, l, if bar.Close > bar.Open { r#"{"color": "rgba(255, 0, 0, 0.2)"}"# } else { r#"{"color": "rgba(255, 0, 0, 0.2)"}"# }); c.hline(bar.High, "{}"); c.plotarrow(bar.Close - bar.Open, "{}"); c.plotshape(bar.Low > 0.0, r#"{"style": "diamond"}"#); c.plotchar(bar.Close > 0.0, r#"{"char": "X"}"#); c.plotcandle(bar.Open * 0.9, bar.High * 0.9, bar.Low * 0.9, bar.Close * 0.9, "{}"); if bar.Close > bar.Open { // long/short/closelong/closeshort c.signal("long", bar.High, 1.5, "long"); } else if bar.Close < bar.Open { c.signal("closelong", bar.Low, 1.5, "closelong"); } c.close(); } }c++// Not supported yet -
Use the
pricePrecisionandvolumePrecisionparameters to control the display precision of the chart data. You can set the display precision for price and volume according to your actual needs. For example, for instruments with large price fluctuations, you can set the precision to 0 to display integers; for instruments with finer price granularity, you can set it to 2 or a higher precision.javascriptfunction main() { // Create the chart control object, setting both price precision and volume precision to 0 (i.e., display integers) let c = KLineChart({ overlay: true, pricePrecision: 0, // Price data precision; set to 2 to keep 2 decimal places volumePrecision: 0 // Volume data precision }) // Select the appropriate trading pair based on the exchange type let symbol = exchange.GetName().includes("Futures_") ? "ETH_USDT.swap" : "ETH_USDT" Log("Test symbol:", symbol) // Get K-line data let bars = exchange.GetRecords(symbol) if (!bars) { return } // Iterate over the K-line data and draw the chart bars.forEach(function(bar, index) { c.begin(bar) c.barcolor(bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.2)') c.plot(bar.High, 'high') c.plot(bar.Low, 'low') c.close(bar) }) }pythondef main(): # Create the chart control object, setting both price precision and volume precision to 0 (i.e., display integers) c = KLineChart({ "overlay": True, "pricePrecision": 0, # Price data precision; set to 2 to keep 2 decimal places "volumePrecision": 0 # Volume data precision }) # Select the appropriate trading pair based on the exchange type exName = exchange.GetName() symbol = "ETH_USDT.swap" if "Futures_" in exName else "ETH_USDT" Log("Test symbol:", symbol) # Get K-line data bars = exchange.GetRecords(symbol) if not bars: return # Iterate over the K-line data and draw the chart for bar in bars: c.begin(bar) c.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)') c.plot(bar.High, 'high') c.plot(bar.Low, 'low') c.close(bar)rustfn main() { // Create the chart control object, setting both price precision and volume precision to 0 (i.e., display integers) // pricePrecision is the price data precision; set to 2 to keep 2 decimal places; volumePrecision is the volume data precision let mut c = KLineChart::new(r#"{"overlay": true, "pricePrecision": 0, "volumePrecision": 0}"#); // Select the appropriate trading pair based on the exchange type let symbol = if exchange.GetName().contains("Futures_") { "ETH_USDT.swap" } else { "ETH_USDT" }; Log!("Test symbol:", symbol); // Get K-line data let bars = exchange.GetRecords(symbol, None, None).unwrap(); // Iterate over the K-line data and draw the chart for bar in &bars { c.begin(bar); c.barcolor(if bar.Close > bar.Open { "rgba(255, 0, 0, 0.2)" } else { "rgba(0, 0, 0, 0.2)" }, "{}"); c.plot(bar.High, r#"{"title": "high"}"#); c.plot(bar.Low, r#"{"title": "low"}"#); c.close(); } }c++// Not supported yet -
The
Pinelanguage drawing interface functions supported in drawing operations are as follows:barcolor: Sets the color of the candlesticks.barcolor(color, offset, editable, show_last, title, display)
The available values for the display parameter are: "none", "all"
javascriptc.barcolor(bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.2)') // The usage is the same as the reference code in the example above, so it will not be repeated herepythonc.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)')rustc.barcolor(if bar.Close > bar.Open { "rgba(255, 0, 0, 0.2)" } else { "rgba(0, 0, 0, 0.2)" }, "{}"); // The usage is the same as the reference code in the example above, so it will not be repeated herec++// Not supported yet -
bgcolor: Fills the candlestick background with the specified color.bgcolor(color, offset, editable, show_last, title, display, overlay)
The available values for the display parameter are: "none", "all"
javascriptc.bgcolor('rgba(0, 255, 0, 0.5)')pythonc.bgcolor('rgba(0, 255, 0, 0.5)')rustc.bgcolor("rgba(0, 255, 0, 0.5)", "{}");c++// Not supported yet -
plot: Plots a series of data on the chart.plot(series, title, color, linewidth, style, trackprice, histbase, offset, join, editable, show_last, display)
The available values for the style parameter are: "stepline_diamond", "stepline", "cross", "areabr", "area", "circles", "columns", "histogram", "linebr", "line"
The available values for the display parameter are: "none", "all"
javascriptc.plot(bar.High, 'high') c.plot(bar.Open < bar.Close ? NaN : bar.Close, "Close", {style: "linebr"}) // Supports plotting discontinuous data linespythonh = c.plot(bar.High, 'high') h = c.plot(None if bar.Open < bar.Close else bar.Close, "Close", style = "linebr") # Supports plotting discontinuous data linesrustlet h = c.plot(bar.High, r#"{"title": "high"}"#); c.plot(if bar.Open < bar.Close { f64::NAN } else { bar.Close }, r#"{"title": "Close", "style": "linebr"}"#); // Supports plotting discontinuous data linesc++// Not supported yet -
fill, fills the background area between two plots orhlines with a specified color. > fill(hline1, hline2, color, title, editable, fillgaps, display) > display parameter options: "none", "all"Since the
JavaScriptlanguage cannot pass arguments by parameter name, to solve this problem you can use the{key: value}structure to pass arguments to specified parameter names. For example, the reference code uses{color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)'}to assign a value to thecolorparameter of thefillfunction.To pass arguments to multiple parameter names consecutively, you can use
{key1: value1, key2: value2, key3: value3}.For example, this sample additionally specifies a
titleparameter:{color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)', title: 'fill'}.Color values can be set either using the
'rgba(255, 0, 0, 0.2)'format or the'#FF0000'format.javascriptlet h = c.plot(bar.High, 'high') let l = c.plot(bar.Low, 'low') c.fill(h, l, {color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)'})pythonh = c.plot(bar.High, 'high') l = c.plot(bar.Low, 'low') c.fill(h, l, color = 'rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(255, 0, 0, 0.2)')rustlet h = c.plot(bar.High, r#"{"title": "high"}"#); let l = c.plot(bar.Low, r#"{"title": "low"}"#); c.fill(h, l, if bar.Close > bar.Open { r#"{"color": "rgba(255, 0, 0, 0.2)"}"# } else { r#"{"color": "rgba(255, 0, 0, 0.2)"}"# });c++// Not supported yet -
hline, draws a horizontal line at a given fixed price level.hline(price, title, color, linestyle, linewidth, editable, display)
linestyle parameter options: "dashed", "dotted", "solid"
display parameter options: "none", "all"
javascriptc.hline(bar.High)pythonc.hline(bar.High)rustc.hline(bar.High, "{}");c++// Not supported yet -
plotarrow, draws up and down arrows on the chart.plotarrow(series, title, colorup, colordown, offset, minheight, maxheight, editable, show_last, display)
display parameter options: "none", "all"
javascriptc.plotarrow(bar.Close - bar.Open)pythonc.plotarrow(bar.Close - bar.Open)rustc.plotarrow(bar.Close - bar.Open, "{}");c++// Not supported yet -
plotshape, draws visual shapes on the chart.plotshape(series, title, style, location, color, offset, text, textcolor, editable, size, show_last, display)
The style parameter can be: "diamond", "square", "label_down", "label_up", "arrow_down", "arrow_up", "circle", "flag", "triangle_down", "triangle_up", "cross", "xcross"
The location parameter can be: "abovebar", "belowbar", "top", "bottom", "absolute"
The size parameter can be: "10px", "14px", "20px", "40px", "80px", corresponding respectively to size.tiny, size.small, size.normal, size.large, and size.huge in the Pine language.
size.auto is equivalent to size.small.
The display parameter can be: "none", "all"
javascriptc.plotshape(bar.Low, {style: 'diamond'})pythonc.plotshape(bar.Low, style = 'diamond')rustc.plotshape(bar.Low > 0.0, r#"{"style": "diamond"}"#);c++// Not supported yet -
plotchar, draws visual shapes on the chart using any given Unicode character.plotchar(series, title, char, location, color, offset, text, textcolor, editable, size, show_last, display)
The location parameter can be: "abovebar", "belowbar", "top", "bottom", "absolute"
The size parameter can be: "10px", "14px", "20px", "40px", "80px", corresponding respectively to size.tiny, size.small, size.normal, size.large, and size.huge in the Pine language.
size.auto is equivalent to size.small.
The display parameter can be: "none", "all"
javascriptc.plotchar(bar.Close, {char: 'X'})pythonc.plotchar(bar.Close, char = 'X')rustc.plotchar(bar.Close > 0.0, r#"{"char": "X"}"#);c++// Not supported yet -
plotcandle, draws a candlestick chart on the chart.plotcandle(open, high, low, close, title, color, wickcolor, editable, show_last, bordercolor, display)
The display parameter can be: "none", "all"
javascriptc.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)pythonc.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)rustc.plotcandle(bar.Open * 0.9, bar.High * 0.9, bar.Low * 0.9, bar.Close * 0.9, "{}");c++// Not supported yet -
signal, this is a function that does not exist in the Pine language; here it is used to draw buy/sell signals.signal(direction, price, qty, id)
The parameter "long" indicates the trade direction, which can be "long", "closelong", "short", or "closeshort". The parameter
bar.Highindicates the position of the signal marker on the Y-axis.The parameter 1.5 indicates the trade quantity of the signal. A fourth parameter can be passed to replace the default text drawn; the default text of the signal marker is the trade direction, for example: "closelong".
javascriptc.signal("long", bar.High, 1.5)pythonc.signal("long", bar.High, 1.5)rustc.signal("long", bar.High, 1.5, "long");c++// Not supported yet -
reset, this is a function that does not exist in the Pine language; it is used to clear chart data.reset(remain)
The
reset()method accepts a parameterremain, used to specify the number of data entries to retain. If theremainparameter is not passed, it means all data will be cleared.javascriptc.reset()pythonc.reset()rustc.reset(0);c++// Not supported yet
Returns
| Type | Description |
object | Chart object. The chart object returned by the |
Arguments
| Name | Type | Required | Description |
options | object / object array | Yes | The
|
See Also
Remarks
For custom drawing in a strategy, you can only choose one of the two methods: the KLineChart() function or the Chart() function. For settings such as colors and styles involved when calling the KLineChart() function, please refer to the topic article on drawing with the KLineChart function.
The pricePrecision and volumePrecision parameters are used to control the display precision of data in the chart. When these parameters are not set, the chart displays data using the default precision. After the precision parameters are set, the price and volume data in the chart will be rounded and displayed according to the specified number of decimal places, which helps simplify the chart display and improve readability.
LogReset
Clear the logs.
LogReset(remain)Examples
javascript
function main() {
// Retain the 10 most recent log entries and clear the rest
LogReset(10)
}
python
def main():
LogReset(10)
rust
fn main() {
// Retain the 10 most recent log entries and clear the rest
LogReset(10);
}
c++
void main() {
LogReset(10);
}Arguments
| Name | Type | Required | Description |
remain | number | No | The |
See Also
Remarks
The startup log generated each time a live trading strategy starts is counted as one entry. Therefore, if no parameter is passed and the strategy produces no log output when it starts, the logs will not be displayed at all, and you will need to wait for the docker to send back the logs (this is normal behavior, not an error).
LogVacuum
Used to reclaim the storage space occupied by deleted data in SQLite after clearing logs with the LogReset() function.
LogVacuum()Examples
javascript
function main() {
LogReset()
LogVacuum()
}
python
def main():
LogReset()
LogVacuum()
rust
fn main() {
LogReset(0);
LogVacuum();
}
c++
void main() {
LogReset();
LogVacuum();
}See Also
Remarks
The reason is that SQLite does not immediately reclaim the occupied storage space when deleting data; the VACUUM command must be executed to clean up the data tables and free up space. This function triggers a file move operation when called, resulting in significant latency, so it is recommended to call it at appropriate time intervals.
console.log
Used to output debug information in the "Debug Info" section of the live trading page. For example, when the live trading ID is 123456, the console.log function outputs debug information on the live trading page while creating a log file with .log extension in the docker directory /logs/storage/123456/ and writing debug information to it. The file name prefix is stdout_.
console.log(...msgs)Examples
javascript
function main() {
console.log("test console.log")
}
python
# 不支持
c++
// 不支持Arguments
| Name | Type | Required | Description |
msg | string / number / bool / object / array / any (any type supported by the platform) | No | The parameter |
See Also
Remarks
Notes:
-
Only
JavaScriptlanguage supports this function. -
Only live trading environment supports this function, neither "Debug Tool" nor "Backtesting System" supports it.
-
When outputting objects, they will be converted to the string
[object Object], so it is recommended to output readable information.
console.error
Used to output error messages in the "Debug Information" section of the live trading page. For example, when the live trading ID is 123456, the console.error function outputs error messages on the live trading page while creating a log file with the prefix stderr_ and extension .log in the docker's directory /logs/storage/123456/ where the live trading belongs, and writes the error messages to this file.
console.error(...msgs)Examples
javascript
function main() {
console.error("test console.error")
}
python
# Not supported
c++
// Not supportedArguments
| Name | Type | Required | Description |
msg | string / number / bool / object / array / any (any type supported by the platform) | No | The parameter |
See Also
Remarks
Notes:
- Only
JavaScriptlanguage supports this function. - Only live trading environment supports this function, "Debug Tool" and "Backtesting System" do not support it.
- When outputting objects, they will be converted to the string
[object Object], it is recommended to output human-readable information.