Type/to search
Built-in Functions
Global
Version
Sleep
IsVirtual
Mail
Mail_Go
SetErrorFilter
GetPid
GetLastError
GetCommand
GetMeta
Dial
HttpQuery
HttpQuery_Go
Encode
UnixNano
Unix
GetOS
MD5
DBExec
UUID
EventLoop
__Serve
_G
_D
_N
_C
_Cross
JSON.parse
JSON.stringify
SetChannelData
GetChannelData
Log
Market
Trade
Account
Futures
NetSettings
Threads
threading
Thread
getThread
mainThread
currentThread
Lock
Condition
Event
Dict
pending
Thread
ThreadLock
ThreadEvent
ThreadCondition
ThreadDict
Web3
TA
Talib
talib.CDL2CROWS
talib.CDL3BLACKCROWS
talib.CDL3INSIDE
talib.CDL3LINESTRIKE
talib.CDL3OUTSIDE
talib.CDL3STARSINSOUTH
talib.CDL3WHITESOLDIERS
talib.CDLABANDONEDBABY
talib.CDLADVANCEBLOCK
talib.CDLBELTHOLD
talib.CDLBREAKAWAY
talib.CDLCLOSINGMARUBOZU
talib.CDLCONCEALBABYSWALL
talib.CDLCOUNTERATTACK
talib.CDLDARKCLOUDCOVER
talib.CDLDOJI
talib.CDLDOJISTAR
talib.CDLDRAGONFLYDOJI
talib.CDLENGULFING
talib.CDLEVENINGDOJISTAR
talib.CDLEVENINGSTAR
talib.CDLGAPSIDESIDEWHITE
talib.CDLGRAVESTONEDOJI
talib.CDLHAMMER
talib.CDLHANGINGMAN
talib.CDLHARAMI
talib.CDLHARAMICROSS
talib.CDLHIGHWAVE
talib.CDLHIKKAKE
talib.CDLHIKKAKEMOD
talib.CDLHOMINGPIGEON
talib.CDLIDENTICAL3CROWS
talib.CDLINNECK
talib.CDLINVERTEDHAMMER
talib.CDLKICKING
talib.CDLKICKINGBYLENGTH
talib.CDLLADDERBOTTOM
talib.CDLLONGLEGGEDDOJI
talib.CDLLONGLINE
talib.CDLMARUBOZU
talib.CDLMATCHINGLOW
talib.CDLMATHOLD
talib.CDLMORNINGDOJISTAR
talib.CDLMORNINGSTAR
talib.CDLONNECK
talib.CDLPIERCING
talib.CDLRICKSHAWMAN
talib.CDLRISEFALL3METHODS
talib.CDLSEPARATINGLINES
talib.CDLSHOOTINGSTAR
talib.CDLSHORTLINE
talib.CDLSPINNINGTOP
talib.CDLSTALLEDPATTERN
talib.CDLSTICKSANDWICH
talib.CDLTAKURI
talib.CDLTASUKIGAP
talib.CDLTHRUSTING
talib.CDLTRISTAR
talib.CDLUNIQUE3RIVER
talib.CDLUPSIDEGAP2CROWS
talib.CDLXSIDEGAP3METHODS
talib.AD
talib.ADOSC
talib.OBV
talib.ACOS
talib.ASIN
talib.ATAN
talib.CEIL
talib.COS
talib.COSH
talib.EXP
talib.FLOOR
talib.LN
talib.LOG10
talib.SIN
talib.SINH
talib.SQRT
talib.TAN
talib.TANH
talib.MAX
talib.MAXINDEX
talib.MIN
talib.MININDEX
talib.MINMAX
talib.MINMAXINDEX
talib.SUM
talib.HT_DCPERIOD
talib.HT_DCPHASE
talib.HT_PHASOR
talib.HT_SINE
talib.HT_TRENDMODE
talib.ATR
talib.NATR
talib.TRANGE
talib.BBANDS
talib.DEMA
talib.EMA
talib.HT_TRENDLINE
talib.KAMA
talib.MA
talib.MAMA
talib.MIDPOINT
talib.MIDPRICE
talib.SAR
talib.SAREXT
talib.SMA
talib.T3
talib.TEMA
talib.TRIMA
talib.WMA
talib.LINEARREG
talib.LINEARREG_ANGLE
talib.LINEARREG_INTERCEPT
talib.LINEARREG_SLOPE
talib.STDDEV
talib.TSF
talib.VAR
talib.ADX
talib.ADXR
talib.APO
talib.AROON
talib.AROONOSC
talib.BOP
talib.CCI
talib.CMO
talib.DX
talib.MACD
talib.MACDEXT
talib.MACDFIX
talib.MFI
talib.MINUS_DI
talib.MINUS_DM
talib.MOM
talib.PLUS_DI
talib.PLUS_DM
talib.PPO
talib.ROC
talib.ROCP
talib.ROCR
talib.ROCR100
talib.RSI
talib.STOCH
talib.STOCHF
talib.STOCHRSI
talib.TRIX
talib.ULTOSC
talib.WILLR
talib.AVGPRICE
talib.MEDPRICE
talib.TYPPRICE
talib.WCLPRICE
OS
Structures
Built-in Variables

The Log() function is used to output logs.

Log(...msgs)

Examples

  • Multiple msg parameters can be passed in:

    javascript
    function main() { Log("msg1", "msg2", "msg3") }
    python
    def main(): Log("msg1", "msg2", "msg3")
    rust
    fn 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.

    javascript
    function 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@") }
    python
    def main(): Log("Hello FMZ Quant !@") Sleep(1000 * 5) Log("Hello, #ff0000@")
    rust
    fn 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 printing base64-encoded images. The content starts with ` and ends with `, for example:

    javascript
    function main() { Log("`data:image/png;base64,AAAA`") }
    python
    def main(): Log("`data:image/png;base64,AAAA`")
    rust
    fn main() { Log!("`data:image/png;base64,AAAA`"); }
    c++
    void main() { Log("`data:image/png;base64,AAAA`"); }
  • The Log() function supports directly printing Python's matplotlib.pyplot object. As long as the object contains a savefig method, it can be printed directly using the Log function, for example:

    python
    import 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:

    javascript
    function main() { Log("[trans]中文|abc[/trans]") }
    python
    def main(): Log("[trans]中文|abc[/trans]")
    rust
    fn main() { Log!("[trans]中文|abc[/trans]"); }
    c++
    void main() { Log("[trans]中文|abc[/trans]"); }

Arguments

NameTypeRequiredDescription

msg

string / number / bool / object / array / any (any type supported by the platform)

No

The parameter msg is the content to be output. Multiple msg parameters can be passed in.

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

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

NameTypeRequiredDescription

profit

number

Yes

The parameter profit is the profit data, which is calculated by the algorithm designed in the strategy.

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 arg parameters can be passed in.

See Also

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

NameTypeRequiredDescription

remain

number

No

The remain parameter is used to specify the number of log entries to retain (an integer).

See Also

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:

    javascript
    function main() { LogStatus('This is a normal status message') LogStatus('This is a red font status message#ff0000') LogStatus('This is a multi-line status message\nI am the second line') }
    python
    def main(): LogStatus('This is a normal status message') LogStatus('This is a red font status message#ff0000') LogStatus('This is a multi-line status message\nI am the second line')
    rust
    fn main() { LogStatus!("This is a normal status message"); LogStatus!("This is a red font status message#ff0000"); LogStatus!("This is a multi-line status message\nI am the second line"); }
    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:

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

    javascript
    function main() { var table = { type: "table", title: "Status Bar Button Styles", cols: ["Default", "Primary", "Success", "Info", "Warning", "Danger"], rows: [ [ {"type":"button", "class": "btn btn-xs btn-default", "name": "Default"}, {"type":"button", "class": "btn btn-xs btn-primary", "name": "Primary"}, {"type":"button", "class": "btn btn-xs btn-success", "name": "Success"}, {"type":"button", "class": "btn btn-xs btn-info", "name": "Info"}, {"type":"button", "class": "btn btn-xs btn-warning", "name": "Warning"}, {"type":"button", "class": "btn btn-xs btn-danger", "name": "Danger"} ] ] } LogStatus("`" + JSON.stringify(table) + "`") }
    python
    import json def main(): table = { "type": "table", "title": "Status Bar Button Styles", "cols": ["Default", "Primary", "Success", "Info", "Warning", "Danger"], "rows": [ [ {"type":"button", "class": "btn btn-xs btn-default", "name": "Default"}, {"type":"button", "class": "btn btn-xs btn-primary", "name": "Primary"}, {"type":"button", "class": "btn btn-xs btn-success", "name": "Success"}, {"type":"button", "class": "btn btn-xs btn-info", "name": "Info"}, {"type":"button", "class": "btn btn-xs btn-warning", "name": "Warning"}, {"type":"button", "class": "btn btn-xs btn-danger", "name": "Danger"} ] ] } LogStatus("`" + json.dumps(table) + "`")
    rust
    fn main() { let table = String::from(r#"{"type": "table", "title": "Status Bar Button Styles", "cols": ["Default", "Primary", "Success", "Info", "Warning", "Danger"], "rows": [["#) + r#"{"type": "button", "class": "btn btn-xs btn-default", "name": "Default"},"# + r#"{"type": "button", "class": "btn btn-xs btn-primary", "name": "Primary"},"# + r#"{"type": "button", "class": "btn btn-xs btn-success", "name": "Success"},"# + r#"{"type": "button", "class": "btn btn-xs btn-info", "name": "Info"},"# + r#"{"type": "button", "class": "btn btn-xs btn-warning", "name": "Warning"},"# + r#"{"type": "button", "class": "btn btn-xs btn-danger", "name": "Danger"}"# + r#"]]}"#; LogStatus!(format!("`{}`", table)); }
    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):

    javascript
    function main() { var table = { type: "table", title: "Status Bar Button Disable and Description Test", cols: ["Column 1", "Column 2", "Column 3"], rows: [] } var button1 = {"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"} var button2 = {"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true} var button3 = {"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false} table.rows.push([button1, button2, button3]) LogStatus("`" + JSON.stringify(table) + "`") }
    python
    import json def main(): table = { "type": "table", "title": "Status Bar Button Disable and Description Test", "cols": ["Column 1", "Column 2", "Column 3"], "rows": [] } button1 = {"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"} button2 = {"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": True} button3 = {"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": False} table["rows"].append([button1, button2, button3]) LogStatus("`" + json.dumps(table) + "`")
    rust
    fn main() { let button1 = r#"{"type": "button", "name": "Button 1", "cmd": "button1", "description": "This is the first button"}"#; let button2 = r#"{"type": "button", "name": "Button 2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true}"#; let button3 = r#"{"type": "button", "name": "Button 3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false}"#; let table = format!( r#"{{"type": "table", "title": "Status Bar Button Disable and Description Test", "cols": ["Column 1", "Column 2", "Column 3"], "rows": [[{}, {}, {}]]}}"#, button1, button2, button3 ); LogStatus!(format!("`{}`", table)); }
    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):

    javascript
    function test1() { Log("Calling custom function") } function main() { while (true) { var table = { type: 'table', title: 'Operation', cols: ['Column 1', 'Column 2', 'Action'], rows: [ ['a', '1', { 'type': 'button', 'cmd': "CoverAll", 'name': 'Close All' }], ['b', '1', { 'type': 'button', 'cmd': 10, 'name': 'Send Number' }], ['c', '1', { 'type': 'button', 'cmd': _D(), 'name': 'Call Function' }], ['d', '1', { 'type': 'button', 'cmd': 'test1', 'name': 'Call Custom Function' }] ] } LogStatus(_D(), "\n", '`' + JSON.stringify(table) + '`') var str_cmd = GetCommand() if (str_cmd) { Log("Received interaction data str_cmd:", "Type:", typeof(str_cmd), "Value:", str_cmd) if(str_cmd == "test1") { test1() } } Sleep(500) } }
    python
    import json def test1(): Log("Calling custom function") def main(): while True: table = { "type": "table", "title": "Operation", "cols": ["Column 1", "Column 2", "Action"], "rows": [ ["a", "1", { "type": "button", "cmd": "CoverAll", "name": "Close All" }], ["b", "1", { "type": "button", "cmd": 10, "name": "Send Number" }], ["c", "1", { "type": "button", "cmd": _D(), "name": "Call Function" }], ["d", "1", { "type": "button", "cmd": "test1", "name": "Call Custom Function" }] ] } LogStatus(_D(), "\n", "`" + json.dumps(table) + "`") str_cmd = GetCommand() if str_cmd: Log("Received interaction data str_cmd", "Type:", type(str_cmd), "Value:", str_cmd) if str_cmd == "test1": test1() Sleep(500)
    rust
    fn test1() { Log!("Calling custom function"); } fn main() { loop { let table = String::from(r#"{"type": "table", "title": "Operation", "cols": ["Column 1", "Column 2", "Action"], "rows": ["#) + r#"["a", "1", {"type": "button", "cmd": "CoverAll", "name": "Close All"}],"# + r#"["b", "1", {"type": "button", "cmd": 10, "name": "Send Number"}],"# + &format!(r#"["c", "1", {{"type": "button", "cmd": "{}", "name": "Call Function"}}],"#, _D(None)) + r#"["d", "1", {"type": "button", "cmd": "test1", "name": "Call Custom Function"}]"# + r#"]}"#; LogStatus!(_D(None), "\n", format!("`{}`", table)); if let Some(str_cmd) = GetCommand(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 input field 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 by defValue), 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 the GetCommand() function to capture the message: open:111.

    javascript
    function 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) } }
    python
    import 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)
    rust
    fn 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 type property in group only supports the following 4 types, and the defValue property 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 name and description in group share the same field names as the name and description in the button structure, their definitions are not the same.
      The name in group is also defined differently from the name in 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 the GetCommand() function when the interaction operation occurs: open:{"orderType":1,"tradePrice":99,"orderAmount":"99","boolean":true}.
    • The type property of button controls only supports "button":
      For button controls that support data input, i.e. controls with the input property set, the type property in the input field configuration supports multiple control types.

    Refer to the following example:

    javascript
    function 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) } }
    python
    import 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)
    rust
    fn 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); } }
  • When the status bar group button control (implemented by setting the group field) and the status bar button control (implemented by setting the input field) 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:

    javascript
    function 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) } }
    python
    import 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)
    rust
    fn 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.

    javascript
    var 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) } }
    python
    import 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)
    rust
    fn 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:

    javascript
    function 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) + '`') }
    python
    import 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) + "`")
    rust
    fn 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:

    javascript
    function 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) + '`') }
    python
    import 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) + "`")
    rust
    fn 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:

    javascript
    function 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]) + '`') }
    python
    import 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]) + "`")
    rust
    fn 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:

    javascript
    function 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") }
    python
    import 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) + "`")
    rust
    fn 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 scroll attribute 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 the scroll attribute 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:

    javascript
    function 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) + "`") }
    python
    import 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) + "`")
    rust
    fn 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

NameTypeRequiredDescription

msg

string / number / bool / object / array / any (any type supported by the platform)

No

The parameter msg is the content to be output. Multiple msg parameters can be passed in.

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.

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

NameTypeRequiredDescription

enable

bool

Yes

When the enable parameter is set to a falsy value (e.g. false), order logs (i.e. the logs generated by functions such as exchange.Buy()) will not be printed, nor will they be written to the live trading database.

See Also

Custom chart plotting function.

Chart(options)

Examples

  • Multi-chart drawing configuration notes:

    • extension.layout property
      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.height property
      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.col property
      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.
    javascript
    function 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]); } }
    python
    import 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() * 100
    rust
    fn 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) } }
    python
    import 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)
    rust
    fn 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) } }
    python
    import 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)
    rust
    fn 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 = t
    rust
    /*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 pie type 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 call c.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

TypeDescription

object

Chart object.

Arguments

NameTypeRequiredDescription

options

object / object array

Yes

The options parameter is the chart configuration. The options parameter of the Chart() function is a JSON-serializable Highcharts.StockChart parameter of HighStocks. Compared with the native parameters, it adds an additional __isStock property. If the __isStock property is set to a falsy value (e.g. false), it is displayed as an ordinary chart, i.e. using a Highcharts chart; if the __isStock property is set to a truthy value (e.g. true), a Highstocks chart is used (by default __isStock is truthy, e.g. true). For details, refer to the HighStocks chart library.

See Also

Remarks

The Chart() function returns a chart object, which contains 4 methods: add(), reset(), update(), del().

    1. update() method:
      The update() method is used to update the chart's configuration information. Its parameter is a Chart chart configuration object (JSON).
    1. del() method:
      The del() method deletes the data series at the specified index according to the passed series parameter.
    1. add() method:
      The add() 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 -1 indicates 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's series[0].data. When the index parameter is not set, it means appending data to the end of the current data series (series).
    1. reset() method:
      The reset() method is used to clear the chart data. It can take one parameter remain, used to specify the number of data entries to retain. When the remain parameter is not passed, it means clearing all data.

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 the KLineChart() 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 overlay is set to a falsy value (for example false), 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 argument overlay as a truthy value (for example true) in the specific function call.

    javascript
    function 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) }) }
    python
    def 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)
    rust
    fn 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 pricePrecision and volumePrecision parameters 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.

    javascript
    function 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) }) }
    python
    def 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)
    rust
    fn 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 Pine language 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"

    javascript
    c.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 here
    python
    c.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)')
    rust
    c.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 here
    c++
    // 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"

    javascript
    c.bgcolor('rgba(0, 255, 0, 0.5)')
    python
    c.bgcolor('rgba(0, 255, 0, 0.5)')
    rust
    c.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"

    javascript
    c.plot(bar.High, 'high') c.plot(bar.Open < bar.Close ? NaN : bar.Close, "Close", {style: "linebr"}) // Supports plotting discontinuous data lines
    python
    h = c.plot(bar.High, 'high') h = c.plot(None if bar.Open < bar.Close else bar.Close, "Close", style = "linebr") # Supports plotting discontinuous data lines
    rust
    let 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 lines
    c++
    // Not supported yet
  • fill, fills the background area between two plots or hlines with a specified color. > fill(hline1, hline2, color, title, editable, fillgaps, display) > display parameter options: "none", "all"

    Since the JavaScript language 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 the color parameter of the fill function.

    To pass arguments to multiple parameter names consecutively, you can use {key1: value1, key2: value2, key3: value3}.

    For example, this sample additionally specifies a title parameter: {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.

    javascript
    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)'})
    python
    h = 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)')
    rust
    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++
    // 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"

    javascript
    c.hline(bar.High)
    python
    c.hline(bar.High)
    rust
    c.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"

    javascript
    c.plotarrow(bar.Close - bar.Open)
    python
    c.plotarrow(bar.Close - bar.Open)
    rust
    c.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"

    javascript
    c.plotshape(bar.Low, {style: 'diamond'})
    python
    c.plotshape(bar.Low, style = 'diamond')
    rust
    c.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"

    javascript
    c.plotchar(bar.Close, {char: 'X'})
    python
    c.plotchar(bar.Close, char = 'X')
    rust
    c.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"

    javascript
    c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)
    python
    c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)
    rust
    c.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.High indicates 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".

    javascript
    c.signal("long", bar.High, 1.5)
    python
    c.signal("long", bar.High, 1.5)
    rust
    c.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 parameter remain, used to specify the number of data entries to retain. If the remain parameter is not passed, it means all data will be cleared.

    javascript
    c.reset()
    python
    c.reset()
    rust
    c.reset(0);
    c++
    // Not supported yet

Returns

TypeDescription

object

Chart object.

The chart object returned by the KLineChart() function contains multiple methods, among which begin(bar) and close(bar) deserve special attention. When iterating over K-line data to perform drawing operations, each drawing operation must start with a call to the begin(bar) function and end with a call to the close(bar) function.

Arguments

NameTypeRequiredDescription

options

object / object array

Yes

The options parameter is a chart configuration object that supports the following properties:

  • overlay: Boolean value, used to set whether the drawing content is overlaid onto the main chart. When set to true, it is displayed on the main chart; when set to false, it is displayed on the sub-chart.

  • pricePrecision: Number, price data precision, used to control the number of decimal places for price data in the chart. For example, setting it to 2 keeps 2 decimal places, and setting it to 0 keeps no decimal places (rounded to an integer).

  • volumePrecision: Number, volume data precision, used to control the number of decimal places for volume data in the chart. For example, setting it to 2 keeps 2 decimal places, and setting it to 0 keeps no decimal places (rounded to an integer).

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.

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

NameTypeRequiredDescription

remain

number

No

The remain parameter is used to set the number of most recent log entries to retain.

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).

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.

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

NameTypeRequiredDescription

msg

string / number / bool / object / array / any (any type supported by the platform)

No

The parameter msg is the content to be output, multiple parameters can be passed.

See Also

Remarks

Notes:

  • Only JavaScript language 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.

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 supported

Arguments

NameTypeRequiredDescription

msg

string / number / bool / object / array / any (any type supported by the platform)

No

The parameter msg is the content to be output, multiple parameters can be passed.

See Also

Remarks

Notes:

  • Only JavaScript language 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.