说明:

参数说明
设置Dial功能的参数 在正常的地址:wss://xxx.xxx.xxx:10441/websocket?compress后以"|“符号分隔,如果参数字符串中有|字符,则以”||“作为分隔符号。各个参数之间用”&"连接,例如ss5代理和压缩参数一起设置:Dial(“wss://baidu.com/stream|proxy=socks5://xxx:9999&compress=gzip_raw&mode=recv”)
用于ws协议时,数据压缩相关的参数:compress=参数值 compress为压缩方式,compress参数,可选gzip_raw,gzip等。非标准gzip。需要使用扩展的方式:gzip_raw,即在分隔符"|“后添加设置compress=gzip_raw,用”&"符号和下一个mode参数分隔。
用于ws协议时,数据压缩相关的参数:mode=参数值 mode为模式,可选dual,send,recv三种。dual为双向,发送压缩数据,接收压缩数据。send为发送压缩数据。recv为接收压缩数据,本地解压缩。
用于设置socks5代理的相关参数:proxy=参数值 proxy为ss5代理设置,参数值格式:socks5://name:pwd@192.168.0.1:1080,name为ss5服务端用户名,pwd为ss5服务端登录密码,1080为ss5服务的端口
用于ws协议时,设置底层自动重连相关的参数:reconnect=参数值 reconnect为是否设置重连,reconnect=true为启用重连,不设置默认不重连
用于ws协议时,设置底层自动重连相关的参数:interval=参数值 interval为重试时间间隔,单位毫秒,interval=10000为重试间隔10秒,不设置默认1秒,即interval=1000
用于ws协议时,设置底层自动重连相关的参数:payload=参数值 payload为ws重连时需要发送的订阅消息,例如:payload=okok
function main(){
    // Dial支持tcp://,udp://,tls://,unix://协议,可加一个参数指定超时的秒数
    var client = Dial("tls://www.baidu.com:443")  
    if (client) {
        // write可再跟一个数字参数指定超时,write返回成功发送的字节数
        client.write("GET / HTTP/1.1\nConnection: Closed\n\n")
        while (true) {
            // read可再跟一个数字参数指定超时,单位:毫秒。返回null指出错或者超时或者socket已经关闭
            var buf = client.read()
            if (!buf) {
                 break
            }
            Log(buf)
        }
        client.close()
    }
}
def main():
    client = Dial("tls://www.baidu.com:443")
    if client:
        client.write("GET / HTTP/1.1\nConnection: Closed\n\n")
        while True:
            buf = client.read()
            if not buf:
                break
            Log(buf)
        client.close()
void main() {
    auto client = Dial("tls://www.baidu.com:443");
    if(client.Valid) {
        client.write("GET / HTTP/1.1\nConnection: Closed\n\n");
        while(true) {
            auto buf = client.read();
            if(buf == "") {
                break;
            }
            Log(buf);
        }
        client.close();
    }
}

read函数支持以下参数:

  • 不传参数时,阻塞到有消息时就返回。例如:ws.read()
  • 传入参数时,单位为毫秒,指定消息等待超时时间。例如:ws.read(2000)指定超时时间为两秒(2000毫秒)。
  • 以下两个参数只对websocket有效: 传入参数-1指不管有无消息,函数立即返回,例如:ws.read(-1)。 传入参数-2指不管有无消息,函数立即返回,但只返回最新的消息,缓冲区的消息会被丢弃。例如ws.read(-2)

read()函数缓冲区说明: ws协议推送的来的数据,如果在策略read()函数调用之间时间间隔过长,就可能造成数据累积。这些数据储存在缓冲区,缓冲区数据结构为队列,上限2000个。超出2000后,最新的数据进入缓冲区,最旧的数据清除掉。

read函数参数 无参数 参数:-1 参数:-2 参数:2000,单位是毫秒
缓冲区已有数据 立即返回最旧数据 立即返回最旧数据 立即返回最新数据 立即返回最旧数据
缓冲区无数据 阻塞到有数据时返回 立即返回空值 立即返回空值 等待2000毫秒,无数据返回空值,有数据则返回
ws连接断开,底层重连时 read()函数返回空字符串,即:"",write()函数返回0,检测到该情况。可以使用close()函数关闭连接,如果设置了自动重连则不用关闭,系统底层会自动重连。

HttpQuery(…)

HttpQuery(Url, PostData, Cookies, Headers, IsReturnHeader),网络URL访问。参数值:全部为字符串类型。 返回值为JSON字符串,JavaScript语言的策略中可以用JSON.parse()函数解析。

注意:

  • HttpQuery(...)函数只支持JavaScript语言。
  • Python语言可以使用urllib库,直接发送http请求。

获取一个Url的返回内容,如果第二个参数PostData为字符串a=1&b=2&c=abc形式,就以POST方式提交。其它例如PUT等方式提交,PostData参数为{method:'PUT', data:'a=1&b=2&c=abc'}PostData参数也可以是JSON字符串。

Cookies这个参数形式为:a=10; b=20,各参数用分号;间隔。 Headers这个参数形式为:User-Agent: Mobile\nContent-Type: text/html各参数用换行符\n间隔。

第二个参数PostData可以自定义方法比如: HttpQuery("http://www.abc.com", {method:'PUT', data:'a=1&b=2&c=abc'}),注意:如果需要给HttpQuery函数设置超时时间,可以在{method:'PUT', data:'a=1&b=2&c=abc'}加入timeout属性(默认60秒)。

设置1秒钟超时: HttpQuery("http://www.abc.com", {method:'PUT', data:'a=1&b=2&c=abc', timeout:1000})

传递Cookie字符串需要第三个参数,但不需要POST请将第二个参数置为空值。模拟测试的时候因为无法模拟访问URL,函数就返回固定字符串Dummy Data。可以用此接口发送短信,或者与其它API接口进行交互。

GET方法调用例子:HttpQuery("http://www.baidu.com")POST方法调用例子:HttpQuery("http://www.163.com", "a=1&b=2&c=abc")

返回Header的调用例子:

HttpQuery("http://www.baidu.com", null, "a=10; b=20", "User-Agent: Mobile\nContent-Type: text/html", true)  // will return {Header: HTTP Header, Body: HTML}
  • HttpQuery函数使用代理设置:

    function main() {
        // 本次设置代理并发送http请求,无用户名,无密码,此次http请求会通过代理发送
        HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/")
    
        // 本次设置代理并发送http请求,输入用户名和密码,仅HttpQuery当前调用生效,之后再次调用HttpQuery("http://www.baidu.com")这样不会使用代理
        HttpQuery("socks5://username:password@127.0.0.1:8889/http://www.baidu.com/")
    }
    
    # HttpQuery不支持Python,可以使用Python的urllib2库
    
    void main() {
        HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/");
        HttpQuery("socks5://username:password@127.0.0.1:8889/http://www.baidu.com/");
    }
    
  • HttpQuery函数的异步版本HttpQuery_Go: 使用方式和exchange.Go函数类似。

    function main() {
        // 创建第一个异步线程
        var r1 = HttpQuery_Go("https://xxx.xxx.xxx")   // https://xxx.xxx.xxx 仅为演示地址
        // 创建第二个异步线程
        var r2 = HttpQuery_Go("https://xxx.xxx.xxx")
        
        // 获取第一个异步线程调用的返回值
        var tickers1 = r1.wait()
        // 获取第二个异步线程调用的返回值
        var tickers2 = r2.wait()
        
        // 打印结果
        Log("tickers1:", tickers1)
        Log("tickers2:", tickers2)
    }
    
    # 不支持
    
    // 不支持
    
  • 回测系统中使用HttpQuery(...)函数: 回测系统中可以使用HttpQuery(...)发送请求(只支持GET请求)获取数据。回测时限制使用20次访问不同的URL,并且HttpQuery(...)访问会缓存数据,相同的URL第二次访问时HttpQuery(...)函数返回缓存数据(不再发生实际网络请求)。

    我们可以在某个服务器或者设备上运行一个服务程序,用来响应策略程序中HttpQuery(...)发送的请求,测试用的Go语言服务程序如下:

    package main
    import (
        "fmt"
        "net/http"
        "encoding/json"
    )
    
    func Handle (w http.ResponseWriter, r *http.Request) {
        defer func() {
            fmt.Println("req:", *r)
            ret := map[string]interface{}{
                "schema" : []string{"time","open","high","low","close","vol"},
                "data" : []interface{}{
                    []int64{1564315200000,9531300,9531300,9497060,9497060,787},
                    []int64{1564316100000,9495160,9495160,9474260,9489460,338},
                },
            }
            b, _ := json.Marshal(ret)
            w.Write(b)
        }()
    }
    

    策略回测时使用HttpQuery(...)函数发送请求:

    function main() {
        // 可以写自己运行服务程序所在设备的IP地址
        Log(HttpQuery("http://xxx.xx.x.xxx:9090/data?msg=hello"));
        Log(exchange.GetAccount());
    }
    
    # HttpQuery不支持Python,可以使用Python的urllib2库
    
    void main() {
        // 可以写自己运行服务程序所在设备的IP地址
        Log(HttpQuery("http://xxx.xx.x.xxx:9090/data?msg=hello"));
        Log(exchange.GetAccount());
    }
    

    img

  • 支持对请求的应答数据进行转码,支持常用编码。 指定PostData参数:{method: "GET",charset:"GB18030"},即可实现应答的数据转码(GB18030)。

Hash(…)

Hash(Algo, OutputAlgo, Data),支持md5/sha256/sha512/sha1的哈希计算,只支持实盘。参数值:全部为字符串类型。 第二个参数可设置为raw/hex/base64,分别指输出加密后的原始内容/hex编码过的数据/base64编码过的数据

function main(){
    Log(Hash("md5", "hex", "hello"))
    Log(Hash("sha512", "base64", "hello"))
}
def main():
    Log(Hash("md5", "hex", "hello"))
    Log(Hash("sha512", "base64", "hello"))
void main() {
    Log(Hash("md5", "hex", "hello"));
    Log(Hash("sha512", "base64", "hello"));
}

HMAC(…)

HMAC(Algo, OutputAlgo, Data, Key),支持md5/sha256/sha512/sha1HMAC加密计算,只支持实盘。参数值:全部为字符串类型。 第二个参数可设置为raw/hex/base64,分别指输出加密后的原始内容/hex编码过的数据/base64编码过的数据

function main(){
    Log(HMAC("md5", "hex", "hello", "pass"))
    Log(HMAC("sha512", "base64", "hello", "pass"))
}
def main():
    Log(HMAC("md5", "hex", "hello", "pass"))
    Log(HMAC("sha512", "base64", "hello", "pass"))
void main() {
    Log(HMAC("md5", "hex", "hello", "pass"));
    Log(HMAC("sha512", "base64", "hello", "pass"));
}

UnixNano()

UnixNano(),返回纳秒级时间戳,如果需要获取毫秒级时间戳,可以使用如下代码:

function main() {
    var time = UnixNano() / 1000000
    Log(_N(time, 0))
}
def main():
    time = UnixNano()
    Log(time)
void main() {
    auto time = UnixNano();
    Log(time);
}

Unix()

Unix(),返回秒级别时间戳。

function main() {
    var t = Unix()
    Log(t)
}
def main():
    t = Unix()
    Log(t)
void main() {
    auto t = Unix();
    Log(t);
}

GetOS()

GetOS(),返回托管者所在系统的信息。

function main() {
    Log("GetOS:", GetOS())
}
def main():
    Log("GetOS:", GetOS())
void main() {
    Log("GetOS:", GetOS());
}

在苹果电脑Mac OS操作系统下运行的托管者日志输出:

GetOS:darwin/amd64

darwinMac OS系统的名称。

MD5(String)

MD5(String),参数值:字符串类型。

function main() {
    Log("MD5", MD5("hello world"))
}
def main():
    Log("MD5", MD5("hello world"))
void main() {
    Log("MD5", MD5("hello world"));
}

日志输出:

MD5 5eb63bbbe01eeed093cb22bb8f5acdc3

DBExec(…)

DBExec(),参数值:可以是字符串、数值、布尔值、空值等类型。返回值:包含SQLite语句执行结果的对象。 数据库接口函数DBExec()通过传入参数,可以操作实盘数据库(SQLite数据库)。实现对实盘数据库中数据的增、删、查、改等操作,支持SQLite语法。实盘数据库中系统保留表:kvdbcfglogprofitchart,切勿对这些表进行操作。注意:DBExec()函数只支持实盘。

  • 支持内存数据库 对于DBExec函数的参数,如果sql语句是以:开头,则在内存数据库中操作,不写文件,速度更快。适合不需要持久化保存的数据库操作,例如:

    function main() {
        var strSql = [
            ":CREATE TABLE TEST_TABLE(", 
            "TS INT PRIMARY KEY NOT NULL,",
            "HIGH REAL NOT NULL,", 
            "OPEN REAL NOT NULL,", 
            "LOW REAL NOT NULL,", 
            "CLOSE REAL NOT NULL,", 
            "VOLUME REAL NOT NULL)"
        ].join("")
        var ret = DBExec(strSql)
        Log(ret)
        
        // 增加一条数据
        Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
        
        // 查询数据
        Log(DBExec(":SELECT * FROM TEST_TABLE;"))
    }
    
    def main():
        arr = [
            ":CREATE TABLE TEST_TABLE(", 
            "TS INT PRIMARY KEY NOT NULL,",
            "HIGH REAL NOT NULL,", 
            "OPEN REAL NOT NULL,", 
            "LOW REAL NOT NULL,", 
            "CLOSE REAL NOT NULL,", 
            "VOLUME REAL NOT NULL)"
        ]
        strSql = ""
        for i in range(len(arr)):
            strSql += arr[i]
        ret = DBExec(strSql)
        Log(ret)
        
        # 增加一条数据
        Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
        
        # 查询数据
        Log(DBExec(":SELECT * FROM TEST_TABLE;"))
    
    void main() {
        string strSql = ":CREATE TABLE TEST_TABLE(\
            TS INT PRIMARY KEY NOT NULL,\
            HIGH REAL NOT NULL,\
            OPEN REAL NOT NULL,\
            LOW REAL NOT NULL,\
            CLOSE REAL NOT NULL,\
            VOLUME REAL NOT NULL)";
        auto ret = DBExec(strSql);
        Log(ret);
        
        // 增加一条数据
        Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"));
        
        // 查询数据
        Log(DBExec(":SELECT * FROM TEST_TABLE;"));
    }
    
  • 创建表

function main() {
    var strSql = [
        "CREATE TABLE TEST_TABLE(", 
        "TS INT PRIMARY KEY NOT NULL,",
        "HIGH REAL NOT NULL,", 
        "OPEN REAL NOT NULL,", 
        "LOW REAL NOT NULL,", 
        "CLOSE REAL NOT NULL,", 
        "VOLUME REAL NOT NULL)"
    ].join("")
    var ret = DBExec(strSql)
    Log(ret)
}
def main():
    arr = [
        "CREATE TABLE TEST_TABLE(", 
        "TS INT PRIMARY KEY NOT NULL,",
        "HIGH REAL NOT NULL,", 
        "OPEN REAL NOT NULL,", 
        "LOW REAL NOT NULL,", 
        "CLOSE REAL NOT NULL,", 
        "VOLUME REAL NOT NULL)"
    ]
    strSql = ""
    for i in range(len(arr)):
        strSql += arr[i]
    ret = DBExec(strSql)
    Log(ret)
void main() {
    string strSql = "CREATE TABLE TEST_TABLE(\
        TS INT PRIMARY KEY NOT NULL,\
        HIGH REAL NOT NULL,\
        OPEN REAL NOT NULL,\
        LOW REAL NOT NULL,\
        CLOSE REAL NOT NULL,\
        VOLUME REAL NOT NULL)";
    auto ret = DBExec(strSql);
    Log(ret);
}
  • 表中记录的增删查改操作
function main() {
    var strSql = [
        "CREATE TABLE TEST_TABLE(", 
        "TS INT PRIMARY KEY NOT NULL,",
        "HIGH REAL NOT NULL,", 
        "OPEN REAL NOT NULL,", 
        "LOW REAL NOT NULL,", 
        "CLOSE REAL NOT NULL,", 
        "VOLUME REAL NOT NULL)"
    ].join("")
    Log(DBExec(strSql))
    
    // 增加一条数据
    Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
    
    // 查询数据
    Log(DBExec("SELECT * FROM TEST_TABLE;"))
    
    // 修改数据
    Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000))    
    
    // 删除数据
    Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110))
}
def main():
    arr = [
        "CREATE TABLE TEST_TABLE(", 
        "TS INT PRIMARY KEY NOT NULL,",
        "HIGH REAL NOT NULL,", 
        "OPEN REAL NOT NULL,", 
        "LOW REAL NOT NULL,", 
        "CLOSE REAL NOT NULL,", 
        "VOLUME REAL NOT NULL)"
    ]
    strSql = ""
    for i in range(len(arr)):
        strSql += arr[i]
    Log(DBExec(strSql))
    
    # 增加一条数据
    Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
    
    # 查询数据
    Log(DBExec("SELECT * FROM TEST_TABLE;"))
    
    # 修改数据
    Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000))
    
    # 删除数据
    Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110))
void main() {
    string strSql = "CREATE TABLE TEST_TABLE(\
        TS INT PRIMARY KEY NOT NULL,\
        HIGH REAL NOT NULL,\
        OPEN REAL NOT NULL,\
        LOW REAL NOT NULL,\
        CLOSE REAL NOT NULL,\
        VOLUME REAL NOT NULL)";
    Log(DBExec(strSql));

    // 增加一条数据
    Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"));
    
    // 查询数据
    Log(DBExec("SELECT * FROM TEST_TABLE;"));
    
    // 修改数据
    Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000));
    
    // 删除数据
    Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110));
}

内置函数

_G(K, V)

_G(K, V),该函数实现了一个可保存的全局字典功能,回测和实盘均支持。回测结束后,保存的数据会被清除。 数据结构为KV表,永久保存在本地文件,每个实盘单独一个数据库,重启或者托管者退出后一直存在。K必须为字符串,不区分大小写,V可以为任何可以JSON序列化的内容。在实盘运行中当调用_G()函数并且不传任何参数时,_G()函数返回当前实盘的ID

function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    // 设置一个全局变量num,值为1
    _G("num", 1)     
    // 更改一个全局变量num,值为字符串ok
    _G("num", "ok")    
    // 删除全局变量num
    _G("num", null)
    // 返回全局变量num的值
    Log(_G("num"))
    // 删除所有全局变量
    _G(null)
    // 返回实盘ID
    var robotId = _G()
}
def main():
    _G("num", 1)     
    _G("num", "ok")    
    _G("num", None)
    Log(_G("num"))
    _G(None)
    robotId = _G()
void main() {
    _G("num", 1);
    _G("num", "ok");
    _G("num", NULL);
    Log(_G("num"));
    _G(NULL);
    // 不支持 auto robotId = _G();
}

注意: 使用_G函数持久化储存数据时,应当根据硬件设备的内存、硬盘空间合理使用,不可滥用。否则可能造成内存溢出的问题。

_D(Timestamp, Fmt)

_D(Timestamp, Fmt),返回指定时间戳对应的时间字符串。参数值:Timestamp为数值类型,值为毫秒数。Fmt为字符串类型,Fmt默认为:yyyy-MM-dd hh:mm:ss,返回值:字符串类型。 返回指定时间戳(毫秒)对应的字符串,不传任何参数就返回当前时间。例如:_D()或者_D(1478570053241),默认格式为yyyy-MM-dd hh:mm:ss

function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    var time = _D()
    Log(time)
}
def main():
    strTime = _D()
    Log(strTime)
void main() {
    auto strTime = _D();
    Log(strTime);
}

注意: Python策略中使用_D()时,需要注意传入的参数为秒级别时间戳(JavaScriptC++策略中为毫秒级别时间戳,1秒等于1000毫秒)。 在实盘时使用_D()函数解析一个时间戳为可读的时间字符串时,需要注意托管者程序所在的操作系统的时区、时间设置。_D()函数解析一个时间戳为可读时间字符串是根据托管者系统的时间而定的。

例如一个时间戳为1574993606000使用代码解析:

function main() {
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    Log(_D(1574993606000))
}
def main():
    # 北京时间的服务器上运行:2019-11-29 10:13:26 ,另一台其它地区的服务器上的托管者运行此代码结果则为:2019-11-29 02:13:26
    Log(_D(1574993606))
void main() {
    Log(_D(1574993606000));
}

_N(Num, Precision)

_N(Num, Precision),格式化一个浮点数。参数值:Num为数值类型,Precision为整型。返回值:数值类型。

例如_N(3.1415, 2)将删除3.1415小数点两位以后的值,函数返回3.14

function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    var i = 3.1415
    Log(i)
    var ii = _N(i, 2)
    Log(ii)
}
def main():
    i = 3.1415
    Log(i)
    ii = _N(i, 2)
    Log(ii)
void main() {
    auto i = 3.1415;
    Log(i);
    auto ii = _N(i, 2);
    Log(ii);
}

如果需要将小数点左边的N个位数都变为0,可以这么写:

function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    var i = 1300
    Log(i)
    var ii = _N(i, -3)
    // 查看日志得知为1000
    Log(ii)
}
def main():
    i = 1300
    Log(i)
    ii = _N(i, -3)
    Log(ii)
void main() {
    auto i = 1300;
    Log(i);
    auto ii = _N(i, -3);
    Log(ii);
}

_C(…)

_C(function, args...),该函数为重试函数,用于获取行情、获取未完成订单等接口的容错。

该接口会一直调用指定函数直到成功返回(参数function引用的函数调用时返回空值或者false会重试调用)。例如_C(exchange.GetTicker),默认重试间隔为3秒,可以调用_CDelay(...)函数来设置重试间隔,例如_CDelay(1000),指改变_C函数重试间隔为1秒。

对于以下函数:

  • exchange.GetTicker()
  • exchange.GetDepth()
  • exchange.GetTrades()
  • exchange.GetRecords()
  • exchange.GetAccount()
  • exchange.GetOrders()
  • exchange.GetOrder()
  • exchange.GetPosition()

都可以通过_C(...)函数来调用进行容错。_C(function, args...)函数不局限于以上列出的函数容错,参数function是函数引用并非函数调用,注意是_C(exchange.GetTicker)并不是_C(exchange.GetTicker())

function main(){
    // 鉴于测试代码,不使用商品期货策略一般架构,这里仅仅判断exchange.IO("status")函数,判断连接期货公司前置机成功后立即执行测试代码。股票证券无需使用exchange.IO("status")判断连接状态
    while(!exchange.IO("status")) {
        Sleep(1000)
    }
    
    // 测试代码部分
    // 设置合约为rb888即螺纹钢主力连续合约,或者设置股票代码
    exchange.SetContractType("rb888")

    var ticker = _C(exchange.GetTicker)
    // 调整_C()函数重试时间间隔为2秒
    _CDelay(2000)
    var depth = _C(exchange.GetDepth)
    Log(ticker)
    Log(depth)
}
def main():
    while not exchange.IO("status"):
        Sleep(1000)

    exchange.SetContractType("rb888")

    ticker = _C(exchange.GetTicker)
    _CDelay(2000)
    depth = _C(exchange.GetDepth)
    Log(ticker)
    Log(depth)
void main() {
    while(exchange.IO("status") == 0) {
        Sleep(1000);
    }

    exchange.SetContractType("rb888");

    auto ticker = _C(exchange.GetTicker);
    _CDelay(2000);
    auto depth = _C(exchange.GetDepth);
    Log(ticker);
    Log(depth);
}

对于有参数的函数使用_C(...)容错时:

function main(){
    // 鉴于测试代码,不使用商品期货策略一般架构,这里仅仅判断exchange.IO("status")函数,判断连接期货公司前置机成功后立即执行测试代码。股票证券无需使用exchange.IO("status")判断连接状态
    while(!exchange.IO("status")) {
        Sleep(1000)
    }
    
    // 测试代码部分
    // 设置合约为rb888即螺纹钢主力连续合约,或者设置股票代码
    exchange.SetContractType("rb888")

    var records = _C(exchange.GetRecords, PERIOD_D1)
    Log(records)
}
def main():
    while not exchange.IO("status"):
        Sleep(1000)

    exchange.SetContractType("rb888")

    records = _C(exchange.GetRecords, PERIOD_D1)
    Log(records)
void main() {
    while(exchange.IO("status") == 0) {
        Sleep(1000);
    }

    exchange.SetContractType("rb888");

    auto records = _C(exchange.GetRecords, PERIOD_D1);
    Log(records);
}

也可以用于自定义函数的容错处理:

var test = function(a, b){
    var time = new Date().getTime() / 1000
    if(time % b == 3){
        Log("符合条件!", "#FF0000")
        return true
    }
    Log("重试!", "#FF0000")
    return false
}

function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试自定义的函数
    var ret = _C(test, 1, 5)
    Log(ret)
}
import time
def test(a, b):
    ts = time.time()
    if ts % b == 3:
        Log("符合条件!", "#FF0000")
        return True
    Log("重试!", "#FF0000")
    return False

def main():
    ret = _C(test, 1, 5)
    Log(ret)
// C++ 不支持这种方式对于自定义函数容错

_Cross(Arr1, Arr2)

_Cross(Arr1, Arr2),返回数组arr1arr2的交叉周期数。正数为上穿周期,负数表示下穿的周期,0指当前价格一样。参数值:数值类型数组。

可以模拟一组数据测试_Cross(Arr1, Arr2)函数:

// 快线指标
var arr1 = [1,2,3,4,5,6,8,8,9]
// 慢线指标
var arr2 = [2,3,4,5,6,7,7,7,7]
function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2))
    Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1))
}
arr1 = [1,2,3,4,5,6,8,8,9]     
arr2 = [2,3,4,5,6,7,7,7,7]
def main():
    Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2))
    Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1))
void main() {
    vector<double> arr1 = {1,2,3,4,5,6,8,8,9};
    vector<double> arr2 = {2,3,4,5,6,7,7,7,7};
    Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2));
    Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1));
}

img

把模拟的数据可视化进行观察

img

具体使用说明:内置函数_Cross分析及使用说明

JSONParse(strJson)

JSONParse(strJson),该函数用于解析JSON字符串。可以正确解析包含有较大数值的JSON字符串,会将较大的数值解析为字符串类型。回测系统不支持该函数。

function main() {
    let s1 = '{"num": 8754613216564987646512354656874651651358}'
    Log("JSON.parse:", JSON.parse(s1))    // JSON.parse: {"num":8.754613216564988e+39}
    Log("JSONParse:", JSONParse(s1))      // JSONParse:  {"num":"8754613216564987646512354656874651651358"}
    
    let s2 = '{"num": 123}'
    Log("JSON.parse:", JSON.parse(s2))    // JSON.parse: {"num":123}
    Log("JSONParse:", JSONParse(s2))      // JSONParse:  {"num":123}
}
import json

def main():
    s1 = '{"num": 8754613216564987646512354656874651651358}'
    Log("json.loads:", json.loads(s1))    # json.loads: map[num:8.754613216564987e+39]
    Log("JSONParse:", JSONParse(s1))      # JSONParse:  map[num:8754613216564987646512354656874651651358]
    
    s2 = '{"num": 123}'
    Log("json.loads:", json.loads(s2))    # json.loads: map[num:123]
    Log("JSONParse:", JSONParse(s2))      # JSONParse:  map[num:123]
void main() {
    auto s1 = "{\"num\":8754613216564987646512354656874651651358}";
    Log("json::parse:", json::parse(s1));
    // Log("JSONParse:", JSONParse(s1));   // 不支持该函数
    
    auto s2 = "{\"num\":123}";
    Log("json::parse:", json::parse(s2));
    // Log("JSONParse:", JSONParse(s2));   // 不支持该函数
}

自定义颜色

每个消息字符串都可以用#ff0000这样的RGB值结尾,代表需要显示的前景色。如果为#ff0000112233这样的格式,则后六位代表背景色。

颜色十六进制图:

function main() {
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    Log("红色", "#FF0000")
}
def main():
    Log("红色", "#FF0000")
void main() {
    Log("红色", "#FF0000");
}

日志信息

实盘运行时日志信息记录在实盘的数据库中,实盘的数据库采用sqlite3数据库,实盘的数据库文件在托管者程序所在设备,数据库文件在托管者程序(robot可执行程序)目录下。例如:ID为130350的实盘数据库文件在../logs/storage/130350这个目录内(..即为robot托管者程序所在目录),数据库文件名为130350.db3。 回测系统中的日志可以在回测结束后,点击回测页面底部右下角的[下载日志]按钮进行下载。 当需要迁移实盘到其它服务器上的托管者时,可以移动实盘的数据库文件(扩展名为db3的数据库文件)到迁移目标服务器上,把文件名设置为平台上对应的实盘ID。这样之前实盘的所有日志信息就不会因为迁移到新设备而丢失。

Log(…)

Log(message),保存一条信息到日志列表。参数值:message可以是任意类型。 如果在字符串后面加上@字符则消息会进入推送队列,推送到当前发明者量化交易平台账号推送设置中配置的邮箱、WebHook等(依次打开控制中心页面、右上角账号设置页面、推送设置页面设置绑定)。

注意:

  • 「调试工具」中不支持推送。
  • 「回测系统」中不支持推送。

推送限制为50条/小时,每条消息推送间隔至少10秒。推送内容重复会被过滤掉。

function main() {
    Log("发明者量化你好 !@")
    Sleep(1000 * 5)
    // 字符串内加入#ff0000,打印日志显示为红色,注意:回测系统不支持推送!
    Log("你好, #ff0000@")
}
def main():
    Log("发明者量化你好 !@")
    Sleep(1000 * 5)
    Log("你好, #ff0000@")
void main() {
    Log("发明者量化你好 !@");
    Sleep(1000 * 5);
    Log("你好, #ff0000@");
}

WebHook推送:

使用Golang编写的服务程序DEMO:

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

设置WebHookhttp://XXX.XX.XXX.XX:9090/data?data=Hello_FMZ

运行服务程序后,执行策略推送信息:

function main() {
    Log("msg", "@")
}
def main():
    Log("msg", "@")
void main() {
    Log("msg", "@");
}

接收到推送,服务程序打印信息:

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}

打印base64编码后的图片 Log函数支持打印base64编码后的图片,以`开头,以 `结尾,例如:

function main() {
    Log("`data:image/png;base64,AAAA`")
}
def main():
    Log("`data:image/png;base64,AAAA`")
void main() {
    Log("`data:image/png;base64,AAAA`");
}

Log支持直接打印Pythonmatplotlib.pyplot对象,只要对象包含savefig方法就可以直接Log打印,例如:

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

打印的日志自动语言切换 Log函数支持语言切换,Log函数输出文本,会根据平台页面上语言设置自动切换为对应的语言,例如:

function main() {
    Log("[trans]中文|abc[/trans]")
}
def main():
    Log("[trans]中文|abc[/trans]")
void main() {
    Log("[trans]中文|abc[/trans]");
}

LogProfit(Profit)

LogProfit(Profit),记录盈亏值,打印盈亏数值并根据盈亏数值绘制收益曲线,参数值:Profit为数值类型。

该函数如果以字符&结尾,只绘制收益图表,不打印收益日志,例如:LogProfit(10, '&')

LogProfitReset()

LogProfitReset(),清空所有收益日志,可以带一个整数数值参数,指定保留的条数。

function main() {
    // 在收益图表上打印30个点,然后重置,只保留最后10个点
    for(var i = 0; i < 30; i++) {
        LogProfit(i)
        Sleep(500)
    }
    LogProfitReset(10)
}
def main():
    for i in range(30):
        LogProfit(i)
        Sleep(500)
    LogProfitReset(10)
void main() {
    for(int i = 0; i < 30; i++) {
        LogProfit(i);
        Sleep(500);
    }
    LogProfitReset(10);
}

LogStatus(Msg)

LogStatus(Msg),此信息不保存到日志列表里,只更新当前实盘的状态信息,在实盘页面日志区域上方的状态栏显示,可多次调用更新状态。参数值:Msg可以为任意类型。

function main() {
    LogStatus('这是一个普通的状态提示')
    LogStatus('这是一个红色字体的状态提示#ff0000')
    LogStatus('这是一个多行的状态信息\n我是第二行')
}
def main():
    LogStatus('这是一个普通的状态提示')
    LogStatus('这是一个红色字体的状态提示#ff0000')
    LogStatus('这是一个多行的状态信息\n我是第二行')
void main() {
    LogStatus("这是一个普通的状态提示");
    LogStatus("这是一个红色字体的状态提示#ff0000");
    LogStatus("这是一个多行的状态信息\n我是第二行");
}

LogStatus(Msg)支持打印base64编码后的图片,以`开头,以`结尾,例如:LogStatus("`data:image/png;base64,AAAA`")LogStatus(Msg)支持直接传入Pythonmatplotlib.pyplot对象,只要对象包含savefig方法就可以传入LogStatus(Msg)函数,例如:

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

状态栏中数据输出示例:

function main() {
    var table = {type: 'table', title: '持仓信息', cols: ['列1', '列2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}
    // JSON序列化后两边加上`字符,视为一个复杂消息格式(当前支持表格)
    LogStatus('`' + JSON.stringify(table) + '`')                    
    // 表格信息也可以在多行中出现
    LogStatus('第一行消息\n`' + JSON.stringify(table) + '`\n第三行消息')
    // 支持多个表格同时显示,将以TAB显示到一组里
    LogStatus('`' + JSON.stringify([table, table]) + '`')
    
    // 也可以构造一个按钮在表格中,策略用GetCommand接收cmd属性的内容                                
    var table = { 
        type: 'table', 
        title: '持仓操作', 
        cols: ['列1', '列2', 'Action'], 
        rows: [ 
            ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': '平仓'}]
        ]
    }
    LogStatus('`' + JSON.stringify(table) + '`') 
    // 或者构造一单独的按钮
    LogStatus('`' + JSON.stringify({'type':'button', 'cmd': 'coverAll', 'name': '平仓'}) + '`') 
    // 可以自定义按钮风格(bootstrap的按钮属性)
    LogStatus('`' + JSON.stringify({'type':'button', 'class': 'btn btn-xs btn-danger', 'cmd': 'coverAll', 'name': '平仓'}) + '`')
}
import json
def main():
    table = {"type": "table", "title": "持仓信息", "cols": ["列1", "列2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]}
    LogStatus('`' + json.dumps(table) + '`')
    LogStatus('第一行消息\n`' + json.dumps(table) + '`\n第三行消息')
    LogStatus('`' + json.dumps([table, table]) + '`')

    table = {
        "type" : "table", 
        "title" : "持仓操作", 
        "cols" : ["列1", "列2", "Action"], 
        "rows" : [
            ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "平仓"}]
        ] 
    }
    LogStatus('`' + json.dumps(table) + '`')
    LogStatus('`' + json.dumps({"type": "button", "cmd": "coverAll", "name": "平仓"}) + '`')
    LogStatus('`' + json.dumps({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "平仓"}) + '`')
void main() {
    json table = R"({"type": "table", "title": "持仓信息", "cols": ["列1", "列2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]})"_json;
    LogStatus("`" + table.dump() + "`");
    LogStatus("第一行消息\n`" + table.dump() + "`\n第三行消息");
    json arr = R"([])"_json;
    arr.push_back(table);
    arr.push_back(table);
    LogStatus("`" + arr.dump() + "`");

    table = R"({
        "type" : "table", 
        "title" : "持仓操作", 
        "cols" : ["列1", "列2", "Action"], 
        "rows" : [
            ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "平仓"}]
        ] 
    })"_json;
    LogStatus("`" + table.dump() + "`");
    LogStatus("`" + R"({"type": "button", "cmd": "coverAll", "name": "平仓"})"_json.dump() + "`");
    LogStatus("`" + R"({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "平仓"})"_json.dump() + "`");
}

设置状态栏按钮的禁用、描述功能:

img

function main() {
    var table = {
        type: "table",
        title: "状态栏按钮的禁用、描述功能测试",
        cols: ["列1", "列2", "列3"], 
        rows: []
    }
    var button1 = {"type": "button", "name": "按钮1", "cmd": "button1", "description": "这是第一个按钮"}
    var button2 = {"type": "button", "name": "按钮2", "cmd": "button2", "description": "这是第二个按钮,设置为禁用", "disabled": true}
    var button3 = {"type": "button", "name": "按钮3", "cmd": "button3", "description": "这是第三个按钮,设置为启用", "disabled": false}
    table.rows.push([button1, button2, button3])
    LogStatus("`" + JSON.stringify(table) + "`")
}
import json
def main():
    table = {
        "type": "table",
        "title": "状态栏按钮的禁用、描述功能测试",
        "cols": ["列1", "列2", "列3"], 
        "rows": []
    }
    button1 = {"type": "button", "name": "按钮1", "cmd": "button1", "description": "这是第一个按钮"}
    button2 = {"type": "button", "name": "按钮2", "cmd": "button2", "description": "这是第二个按钮,设置为禁用", "disabled": True}
    button3 = {"type": "button", "name": "按钮3", "cmd": "button3", "description": "这是第三个按钮,设置为启用", "disabled": False}
    table["rows"].append([button1, button2, button3])
    LogStatus("`" + json.dumps(table) + "`")
void main() {
    json table = R"({
        "type": "table",
        "title": "状态栏按钮的禁用、描述功能测试",
        "cols": ["列1", "列2", "列3"], 
        "rows": []
    })"_json;
    json button1 = R"({"type": "button", "name": "按钮1", "cmd": "button1", "description": "这是第一个按钮"})"_json;
    json button2 = R"({"type": "button", "name": "按钮2", "cmd": "button2", "description": "这是第二个按钮,设置为禁用", "disabled": true})"_json;
    json button3 = R"({"type": "button", "name": "按钮3", "cmd": "button3", "description": "这是第三个按钮,设置为启用", "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() + "`");
}

状态栏按钮样式设置:

img

function main() {
    var table = {
        type: "table",
        title: "状态栏按钮样式",
        cols: ["默认", "原始", "成功", "信息", "警告", "危险"], 
        rows: [
            [
                {"type":"button", "class": "btn btn-xs btn-default", "name": "默认"},
                {"type":"button", "class": "btn btn-xs btn-primary", "name": "原始"},
                {"type":"button", "class": "btn btn-xs btn-success", "name": "成功"},
                {"type":"button", "class": "btn btn-xs btn-info", "name": "信息"},
                {"type":"button", "class": "btn btn-xs btn-warning", "name": "告警"},
                {"type":"button", "class": "btn btn-xs btn-danger", "name": "危险"}
            ]
        ]
    }
    LogStatus("`" + JSON.stringify(table) + "`")
}
import json
def main():
    table = {
        "type": "table",
        "title": "状态栏按钮样式",
        "cols": ["默认", "原始", "成功", "信息", "警告", "危险"], 
        "rows": [
            [
                {"type":"button", "class": "btn btn-xs btn-default", "name": "默认"},
                {"type":"button", "class": "btn btn-xs btn-primary", "name": "原始"},
                {"type":"button", "class": "btn btn-xs btn-success", "name": "成功"},
                {"type":"button", "class": "btn btn-xs btn-info", "name": "信息"},
                {"type":"button", "class": "btn btn-xs btn-warning", "name": "告警"},
                {"type":"button", "class": "btn btn-xs btn-danger", "name": "危险"}
            ]
        ]
    }
    LogStatus("`" + json.dumps(table) + "`")
void main() {
    json table = R"({
        "type": "table",
        "title": "状态栏按钮样式",
        "cols": ["默认", "原始", "成功", "信息", "警告", "危险"], 
        "rows": [
            [
                {"type":"button", "class": "btn btn-xs btn-default", "name": "默认"},
                {"type":"button", "class": "btn btn-xs btn-primary", "name": "原始"},
                {"type":"button", "class": "btn btn-xs btn-success", "name": "成功"},
                {"type":"button", "class": "btn btn-xs btn-info", "name": "信息"},
                {"type":"button", "class": "btn btn-xs btn-warning", "name": "告警"},
                {"type":"button", "class": "btn btn-xs btn-danger", "name": "危险"}
            ]
        ]
    })"_json;
    LogStatus("`" + table.dump() + "`");
}

结合GetCommand()函数,构造状态栏按钮交互功能:

function test1() {
    Log("调用自定义函数")
}

function main() {
    while (true) {
        var table = {
            type: 'table',
            title: '操作',
            cols: ['列1', '列2', 'Action'],
            rows: [
                ['a', '1', {
                    'type': 'button',                       
                    'cmd': "CoverAll",                      
                    'name': '平仓'                           
                }],
                ['b', '1', {
                    'type': 'button',
                    'cmd': 10,                              
                    'name': '发送数值'
                }],
                ['c', '1', {
                    'type': 'button',
                    'cmd': _D(),                          
                    'name': '调用函数'
                }],
                ['d', '1', {
                    'type': 'button',
                    'cmd': 'test1',       
                    'name': '调用自定义函数'
                }]
            ]
        }
        LogStatus(_D(), "\n", '`' + JSON.stringify(table) + '`')

        var str_cmd = GetCommand()
        if (str_cmd) {
            Log("接收到的交互数据 str_cmd:", "类型:", typeof(str_cmd), "值:", str_cmd)
            if(str_cmd == "test1") {
                test1()
            }
        }

        Sleep(500)
    }
}
import json
def test1():
    Log("调用自定义函数")

def main():
    while True:
        table = {
            "type": "table", 
            "title": "操作", 
            "cols": ["列1", "列2", "Action"],
            "rows": [
                ["a", "1", {
                    "type": "button", 
                    "cmd": "CoverAll",
                    "name": "平仓"
                }],
                ["b", "1", {
                    "type": "button",
                    "cmd": 10,
                    "name": "发送数值" 
                }], 
                ["c", "1", {
                    "type": "button",
                    "cmd": _D(),
                    "name": "调用函数" 
                }],
                ["d", "1", {
                    "type": "button",
                    "cmd": "test1",
                    "name": "调用自定义函数" 
                }]
            ]
        }

        LogStatus(_D(), "\n", "`" + json.dumps(table) + "`")
        str_cmd = GetCommand()
        if str_cmd:
            Log("接收到的交互数据 str_cmd", "类型:", type(str_cmd), "值:", str_cmd)
            if str_cmd == "test1":
                test1()
        Sleep(500)
void test1() {
    Log("调用自定义函数");
}

void main() {
    while(true) {
        json table = R"({
            "type": "table", 
            "title": "操作", 
            "cols": ["列1", "列2", "Action"],
            "rows": [
                ["a", "1", {
                    "type": "button", 
                    "cmd": "CoverAll",
                    "name": "平仓"
                }],
                ["b", "1", {
                    "type": "button",
                    "cmd": 10,
                    "name": "发送数值" 
                }], 
                ["c", "1", {
                    "type": "button",
                    "cmd": "",
                    "name": "调用函数" 
                }],
                ["d", "1", {
                    "type": "button",
                    "cmd": "test1",
                    "name": "调用自定义函数" 
                }]
            ]
        })"_json;
        table["rows"][2][2]["cmd"] = _D();
        LogStatus(_D(), "\n", "`" + table.dump() + "`");
        auto str_cmd = GetCommand();
        if(str_cmd != "") {
            Log("接收到的交互数据 str_cmd", "类型:", typeid(str_cmd).name(), "值:", str_cmd);
            if(str_cmd == "test1") {
                test1();
            }
        }
        Sleep(500);
    }
}

在构造状态栏按钮进行交互时也支持输入数据,交互指令最终由GetCommand()函数捕获。 给状态栏中的按钮控件的数据结构中增加input项,例如给{"type": "button", "cmd": "open", "name": "开仓"}增加"input": {"name": "开仓数量", "type": "number", "defValue": 1},就可以使按钮在被点击时弹出一个带输入框控件的弹窗(输入框中默认值为1,即defValue设置的数据),可以输入一个数据和按钮命令一起发送。例如以下测试代码运行时,在点击「开仓」按钮后,弹出一个带输入框的弹窗,在输入框中输入111后点击「确定」。GetCommand函数就会捕获消息:open:111

function main() {
    var tbl = {
        type: "table",
        title: "操作",
        cols: ["列1", "列2"],
        rows: [
            ["开仓操作", {"type": "button", "cmd": "open", "name": "开仓", "input": {"name": "开仓数量", "type": "number", "defValue": 1}}],
            ["平仓操作", {"type": "button", "cmd": "coverAll", "name": "全部平仓"}]
        ] 
    }

    LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`")
    while (true) {
        var cmd = GetCommand()
        if (cmd) {
            Log("cmd:", cmd)
        }
        Sleep(1000)
    }
}
import json

def main():
    tbl = {
        "type": "table", 
        "title": "操作", 
        "cols": ["列1", "列2"],
        "rows": [
            ["开仓操作", {"type": "button", "cmd": "open", "name": "开仓", "input": {"name": "开仓数量", "type": "number", "defValue": 1}}],
            ["平仓操作", {"type": "button", "cmd": "coverAll", "name": "全部平仓"}]
        ]
    }

    LogStatus(_D(), "\n", "`" + json.dumps(tbl) +