oparameter is set tosignorsignTx, thekey``` parameter is required.

function main(){
    Log(Encode("md5", "raw", "hex", "hello"))
    Log(Encode("sha512", "raw", "base64", "hello"))
    Log(Encode("keccak256", "raw", "hex", "unwrapWETH9(uint256,address)"))

    Log(Encode("raw", "string", "hex", "example"))          // 6578616d706c65
    Log(Encode("raw", "hex", "string", "6578616d706c65"))   // example
}
def main():
    Log(Encode("md5", "raw", "hex", "hello", "", ""))
    Log(Encode("sha512", "raw", "base64", "hello", "", ""))
    Log(Encode("keccak256", "raw", "hex", "unwrapWETH9(uint256,address)", "", ""))

    Log(Encode("raw", "string", "hex", "example", "", ""))
    Log(Encode("raw", "hex", "string", "6578616d706c65", "", ""))
void main(){
    Log(Encode("md5", "raw", "hex", "hello"));
    Log(Encode("sha512", "raw", "base64", "hello"));
    Log(Encode("keccak256", "raw", "hex", "unwrapWETH9(uint256,address)"));
    
    Log(Encode("raw", "string", "hex", "example"));          // 6578616d706c65
    Log(Encode("raw", "hex", "string", "6578616d706c65"));   // example
}

The parameter algo also supports encoding and decoding of strings, such as text.encoder.utf8, text.decoder.utf8, text.encoder.gbk and text.decoder.gbk.

function main(){
    var ret1 = Encode("text.encoder.utf8", "raw", "hex", "hello")     // e4bda0e5a5bd
    Log(ret1)    
    var ret2 = Encode("text.decoder.utf8", "hex", "string", ret1)   
    Log(ret2)

    var ret3 = Encode("text.encoder.gbk", "raw", "hex", "hello")      // c4e3bac3
    Log(ret3)
    var ret4 = Encode("text.decoder.gbk", "hex", "string", ret3)
    Log(ret4)
}
def main():
    ret1 = Encode("text.encoder.utf8", "raw", "hex", "hello", "", "")     # e4bda0e5a5bd
    Log(ret1)    
    ret2 = Encode("text.decoder.utf8", "hex", "string", ret1, "", "")   
    Log(ret2)

    ret3 = Encode("text.encoder.gbk", "raw", "hex", "hello", "", "")      # c4e3bac3
    Log(ret3)
    ret4 = Encode("text.decoder.gbk", "hex", "string", ret3, "", "")
    Log(ret4)
void main(){
    auto ret1 = Encode("text.encoder.utf8", "raw", "hex", "hello");     // e4bda0e5a5bd
    Log(ret1);    
    auto ret2 = Encode("text.decoder.utf8", "hex", "string", ret1);   
    Log(ret2);

    auto ret3 = Encode("text.encoder.gbk", "raw", "hex", "hello");      // c4e3bac3
    Log(ret3);
    auto ret4 = Encode("text.decoder.gbk", "hex", "string", ret3);
    Log(ret4);
}

UnixNano()

UnixNano() returns nanosecond-level timestamp; if you need to obtain the millisecond-level timestamp, you can use the following code:

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() returns a timestamp in seconds.

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

GetOS()

GetOS() returns information about the system where the docker is located.

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

The log output of the docker run by Mac OS of Apple computer:

GetOS:darwin/amd64

darwin is the name of Mac OS system.

MD5(String)

MD5(String); parameter value: string type.

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

Log output:

MD5 5eb63bbbe01eeed093cb22bb8f5acdc3

DBExec(…)

DBExec(), its parameter value could be string, number or boolean, null and other types; return value: the object with the execution results in SQLite language. DBExec(), the interface function of database, through passing parameters, can run the database of the bot (SQLite database). It realizes the adding, deleting, querying, and modifying operations on the bot database, supporting SQLite grammar. The system in the bot database save tables including: kvdb, cfg, log, profit and chart; do not operate any tables mentioned above. Note: the function DBExec() only supports the real bot.

  • Supports memory database For the parameters of function DBExec, if the sql statement is started with :, the operations in the memory database will be faster without writing files. It is suitable for the database operations without needs for long-term storage, for example:

    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)
        
        // Add a piece of data
        Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
        
        // Query the data
        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)
        
        # Add a piece of data
        Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
        
        # Query the data
        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);
        
        // Add a piece of data
        Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"));
        
        // Query the data
        Log(DBExec(":SELECT * FROM TEST_TABLE;"));
    }
    
  • Create a 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);
}
  • The adding, deleting, querying and modifying operations recorded in the 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("")
    Log(DBExec(strSql))
    
    // Add a piece of data
    Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
    
    // Query the data
    Log(DBExec("SELECT * FROM TEST_TABLE;"))
    
    // Modify the data
    Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000))    
    
    // Delete the data
    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))
    
    # Add a piece of data
    Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
    
    # Query the data
    Log(DBExec("SELECT * FROM TEST_TABLE;"))
    
    # Modify the data
    Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000))
    
    # Delete the data
    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));

    // Add a piece of data
    Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"));
    
    // Query the data
    Log(DBExec("SELECT * FROM TEST_TABLE;"));
    
    // Modify the data
    Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000));
    
    // Delete the data
    Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110));
}

UUID()

UUID(), returns a 32-bit unique UUID, this function is available for real bots only.

function main() {
    var uuid1 = UUID()
    var uuid2 = UUID()
    Log(uuid1, uuid2)
}
def main():
    uuid1 = UUID()
    uuid2 = UUID()
    Log(uuid1, uuid2)
void main() {
    auto uuid1 = UUID();
    auto uuid2 = UUID();
    Log(uuid1, uuid2);
}

EventLoop(timeout)

EventLoop(timeout), returns after there is any websocket readable or exchange.Go, HttpQuery_Go and other concurrent tasks are completed. If the parameter timeout is set to 0, wait for an event to occur before returning. If it is greater than 0, set the event waiting timeout. If it is less than 0, return the latest event immediately. If the returned object is not null, the Event contained in the returned content is the triggered event type. This function is available for real bot trading only.

The first call of EventLoop in the code will initialize the mechanism of listening to the event. If the first call of EventLoop is started after the event callback, the previous event will be missed. The queue structure encapsulated by the underlying system will cache up to 500 event callbacks. If the EventLoop is not called in time for removal during program execution, the later event callbacks beyond the 500 cache will be lost. The call of EventLoop function will not affect the cache queue of websocket at the underlying system, nor the cache of exchange.Go and other concurrent functions. For these caches, you still need to use their own methods to retrieve data. For the data that has been taken out before the EventLoop function returns, no return event will be generated in the EventLoop function.

The main purpose of the EventLoop function is to notify the strategy layer that the underlying system has received new network data. Drive the whole strategy with events. When the EventLoop function returns an event, you only need to traverse all data sources. For example, the objects created by websocket connection and objects created by exchange.Go try to get data. You can refer to an open source class library design: class library link.

function main() {
    var routine_getTicker = exchange.Go("GetTicker")
    var routine_getDepth = exchange.Go("GetDepth")
    var routine_getTrades = exchange.Go("GetTrades")
    
     // Sleep(2000), if the Sleep statement is used here, the subsequent EventLoop function will miss the previous events, because after waiting for 2 seconds, the concurrent function has received the data, and the EventLoop monitoring mechanism will start later, and these events will be missed
     // Unless you start calling EventLoop(-1) on the first line of code, first initialize the listening mechanism of EventLoop, you will not miss these events

     // Log("GetDepth:", routine_getDepth.wait()) If the wait function is called in advance to get the result of the concurrent call of the GetDepth function, the event that the GetDepth function receives the request result will not be returned in the EventLoop function
    var ts1 = new Date().getTime()
    var ret1 = EventLoop(0)
    
    var ts2 = new Date().getTime()
    var ret2 = EventLoop(0)
    
    var ts3 = new Date().getTime()
    var ret3 = EventLoop(0)
    
    Log("The first concurrent task completed was:", _D(ts1), ret1)
    Log("The second concurrent task completed was:", _D(ts2), ret2)
    Log("The third concurrent task completed was:", _D(ts3), ret3)
    
    Log("GetTicker:", routine_getTicker.wait())
    Log("GetDepth:", routine_getDepth.wait())
    Log("GetTrades:", routine_getTrades.wait())
}
import time
def main():
    routine_getTicker = exchange.Go("GetTicker")
    routine_getDepth = exchange.Go("GetDepth")
    routine_getTrades = exchange.Go("GetTrades")
    
    ts1 = time.time()
    ret1 = EventLoop(0)
    
    ts2 = time.time()
    ret2 = EventLoop(0)
    
    ts3 = time.time()
    ret3 = EventLoop(0)
    
    Log("The first concurrent task completed was:", _D(ts1), ret1)
    Log("The second concurrent task completed was:", _D(ts2), ret2)
    Log("The third concurrent task completed was:", _D(ts3), ret3)
    
    Log("GetTicker:", routine_getTicker.wait())
    Log("GetDepth:", routine_getDepth.wait())
    Log("GetTrades:", routine_getTrades.wait())
void main() {
    auto routine_getTicker = exchange.Go("GetTicker");
    auto routine_getDepth = exchange.Go("GetDepth");
    auto routine_getTrades = exchange.Go("GetTrades");
    
    auto ts1 = Unix() * 1000;
    auto ret1 = EventLoop(0);
    
    auto ts2 = Unix() * 1000;
    auto ret2 = EventLoop(0);
    
    auto ts3 = Unix() * 1000;
    auto ret3 = EventLoop(0);
    
    Log("The first concurrent task completed was:", _D(ts1), ret1);
    Log("The second concurrent task completed was:", _D(ts2), ret2);
    Log("The third concurrent task completed was:", _D(ts3), ret3);
    
    Ticker ticker;
    Depth depth;
    Trades trades;
    routine_getTicker.wait(ticker);
    routine_getDepth.wait(depth);
    routine_getTrades.wait(trades);
    
    Log("GetTicker:", ticker);
    Log("GetDepth:", depth);
    Log("GetTrades:", trades);
}

Built-In Functions

_G(K, V)

_G(K, V), with a function of a global dictionary that can be saved, supports both backtest and bot. After the backtest, the saved data will be cleared. The data structure is KV table, stored in a local file permanently. Each bot has a separate database. It will always exists after restarting or when the docker exits. K must be a string, which is not case sensitive. V can be any JSON serializable content. When the function _G() is called and no parameters are passed in the bot operation, the function _G() returns the ID of the current bot.

function main(){
    // Set a global variable num with a value of 1
    _G("num", 1)     
    // Change a global variable num with the value "ok"
    _G("num", "ok")    
    // Delete global variable num
    _G("num", null)
    // Return the value of the global variable num
    Log(_G("num"))
    // Delete all global variables
    _G(null)
    // Return bot 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);
    // does not support auto robotId = _G();
}

Note: When use the _G function to save data, it should be used reasonably according to the memory and hard disk space of the hardware device, and should not be abused. Otherwise, it may cause an memory overflow problem.

_D(Timestamp, Fmt)

_D(Timestamp, Fmt), returns the corresponding time strings of the specified timestamp. Parameter value: Timestamp is a numeric type, in millisecond. Fmt is a string type; Fmt defaults to : yyyy-MM-dd hh:mm:ss; return value: string type. It returns the specified timestamp(ms) string, and returns the current time without passing any parameters; for example: _D() or _D(1478570053241), of which the default format is yyyy-MM-dd hh:mm:ss.

function main(){
    var time = _D()
    Log(time)
}
def main():
    strTime = _D()
    Log(strTime)
void main() {
    auto strTime = _D();
    Log(strTime);
}

Note: When use _D() in the Python strategy, we need to pay attention that the parameters passed in are timestamps in second (the millisecond level timestamps in the JavaScript and C ++ strategies, and 1 second = 1000 milliseconds). In the bot, when using the function _D() to parse a time string with a readable timestamp, you need to pay attention to the time zone in the operation system of the docker program. The function _D() parses a timestamp as a readable time string based on the time of the docker system.

For example, parsing a timestamp of 1574993606000 with code:

function main() {
    Log(_D(1574993606000))
}
def main():
    # Beijing time server runs: 2019-11-29 10:13:26, and the docker on another server in another region runs this code will get the results: 2019-11-29 02:13:26
    Log(_D(1574993606))
void main() {
    Log(_D(1574993606000));
}

_N(Num, Precision)

_N(Num, Precision), a formatted floating point number. Parameter value: Num is of numeric type; Precision is of integer type. Return value: numeric type.

For example: _N(3.1415, 2) will delete the value after two decimal places of 3.1415 and return 3.14.

function main(){
    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);
}

If you need to change N digits on the left of the decimal point to 0, you can write:

function main(){
    var i = 1300
    Log(i)
    var ii = _N(i, -3)
    // Checking the log shows that it is 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…) is a retry function, used for the fault tolerance of the interfaces about obtaining market information and acquiring unfinished orders, etc.

The interface will call the specified function continuously until it returns successfully (parameter function returns null value when calling the referenced function or false will retry the call). For example, _ C(exchange. GetTicker), the default retry interval is 3 seconds, which can call the function _CDelay (...) to set the retry interval, such as _CDelay (1000) means change function _C retry interval to 1 second.

For the following functions:

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

They can all be called to do fault tolerance by function _C(...). The function _C(function, args...) is not limited to the fault tolerance of the functions listed above. The parameter function is quoted not called, and pay attention that it is _C(exchange.GetTicker), not _C(exchange.GetTicker()).

function main(){
    var ticker = _C(exchange.GetTicker)
    // Adjust _C() function's retry interval to 2 seconds
    _CDelay(2000)
    var depth = _C(exchange.GetDepth)
    Log(ticker)
    Log(depth)
}
def main():
    ticker = _C(exchange.GetTicker)
    _CDelay(2000)
    depth = _C(exchange.GetDepth)
    Log(ticker)
    Log(depth)
void main() {
    auto ticker = _C(exchange.GetTicker);
    _CDelay(2000);
    auto depth = _C(exchange.GetDepth);
    Log(ticker);
    Log(depth);
}

For functions with parameters, when using _C(...) to do fault tolerance:

function main(){
    var records = _C(exchange.GetRecords, PERIOD_D1)
    Log(records)
}
def main():
    records = _C(exchange.GetRecords, PERIOD_D1)
    Log(records)
void main() {
    auto records = _C(exchange.GetRecords, PERIOD_D1);
    Log(records);
}

It can also be used for custom function fault tolerance handling:

var test = function(a, b){
    var time = new Date().getTime() / 1000
    if(time % b == 3){
        Log("Meet the criteria! ", "#FF0000")
        return true
    }
    Log("Retry!", "#FF0000")
    return false
}

function main(){
    var ret = _C(test, 1, 5)
    Log(ret)
}
import time
def test(a, b):
    ts = time.time()
    if ts % b == 3:
        Log("Meet the criteria!", "#FF0000")
        return True
    Log("Retry!", "#FF0000")
    return False

def main():
    ret = _C(test, 1, 5)
    Log(ret)
// C++ does not support this method for fault tolerance of custom functions.

_Cross(Arr1, Arr2)

_Cross(Arr1, Arr2) returns the number of crossing periods of the arrays arr1 and arr2. A positive number is the period of upswing, and a negative number is the period of downswing, and 0 means the same as the current price. Parameter value: array of number type.

You can simulate a set of data to test the function _Cross(Arr1, Arr2):

// Fast line indicator
var arr1 = [1,2,3,4,5,6,8,8,9]
// Slow line indicator
var arr2 = [2,3,4,5,6,7,7,7,7]
function main(){
    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

Visualize the simulated data for observation

img

Specific instructions: Built-In Function _Cross Analysis and Instructions

JSONParse(strJson)

JSONParse(strJson), the function is used to parse JSON strings. JSON strings containing large numbers can be parsed correctly, and large numbers will be parsed into string types. The backtesting system does not support this function.

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));   // The function is not supported
    
    auto s2 = "{\"num\":123}";
    Log("json::parse:", json::parse(s2));
    // Log("JSONParse:", JSONParse(s2));   // The function is not supported
}

Custom Color

Each message string can end up with an RGB value such as #ff0000, which represents the foreground color to be displayed. If it is in a format such as #ff0000112233, the last six posteriors represent the background color.

function main() {
    Log("Red", "#FF0000")
}
def main():
    Log("Red", "#FF0000")
void main() {
    Log("Red", "#FF0000");
}

Log Information

When the bot is running, the log information is recorded in the database of the bot, which adopts the sqlite3 database. The database files locate in the device with the docker program, and the exact location of the files is in the dictionary of the docker program (robot executable program). For example: The bot database file with ID 130350 is in the directory ../logs/storage/130350 (.. is the dictionary where the docker of the robot is located), and the database file name is 130350.db3.

The logs in the backtest system can be downloaded by clicking the [Download Log] button at the bottom right corner of the backtest page after the backtest is over. When you need to transfer the bot to a docker on another server, you can move the database files of the bot (database files with the extension “db3”) to the transfer target server, and set the file name to the corresponding bot ID on the platform. In this way, all log information of the previous bot will not be lost due to the migration to the new device.

Log(…)

Log(message) means saving a message to the log list. Parameter value: message can be any type. If you add the character @ after the string, the message will enter the push queue and be pushed to the current WeChat account of FMZ Quant Trading platform, and the “Email”, “Telegram” and “WebHook” in the “Push settings” will be pushed (open the pages of Dashboard, Account and Push settings by orders to set bindings).

Note:

  • Push is not supported in the “Debug Tool”.
  • Push is not supported in the “Backtest System”.
function main() {
    Log("Hello FMZ Quant!@")
    Sleep(1000 * 5)
    // Add the string to #ff0000, print the log in red, and push the message
    Log("Hello, #ff0000@")
}
def main():
    Log("Hello FMZ Quant!@")
    Sleep(1000 * 5)
    Log("Hello, #ff0000@")
void main() {
    Log("Hello FMZ Quant!@");
    Sleep(1000 * 5);
    Log("Hello, #ff0000@");
}

WebHook push:

Use the service program DEMO written in 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 WebHook: http://XXX.XX.XXX.XX:9090/data?data=Hello_FMZ

After run the service program, execute the strategy and push the information:

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

Receive the push information, and the service program print the information:

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}

Print the base64 encoded image The function Log supports printing the images coded in base64, starts them with `, and ending with `, for example:

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 supports printing the matplotlib.pyplot objects of Python directly, that is, as long as the objects contain savefig method, you can use Log to print directly, for example:

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

Automatic language switching of printed logs The function Log supports language switching; when the function outputs text, it will switch to the corresponding language automatically according to the language setting on the platform page. For example:

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

LogProfit(Profit)

LogProfit(Profit) records the profit value, prints the profit value, and draw a profit curve according to the profit value. Parameter value: Profit is numeric type.

If the function ends up with the character &, it can only realize drawing the profit chart, and not printing the profit log, such as: LogProfit(10, '&').

LogProfitReset()

LogProfitReset() clears all profit logs; you can take an integer value parameter to specify the number of reserved items.

function main() {
    // Print 30 points on the income chart, then reset, and only retain the last 10 points 
    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), the information is not saved in the log list, only the current status information of the bot is updated; it is displayed above the log and can be called multiple times to update the status. Parameter value: Msg can be any type.

function main() {
    LogStatus('This is a normal status prompt')
    LogStatus('This is a status prompt in red font # ff0000')
    LogStatus('This is a multi-line status message \n I am the second line')
}
def main():
    LogStatus('This is a normal status prompt')
    LogStatus('This is a status prompt in red font # ff0000')
    LogStatus('This is a multi-line status message \nI am the second line')
void main() {
    LogStatus("This is a normal status prompt");
    LogStatus("This is a status prompt in red font # ff0000");
    LogStatus("This is a multi-line status message \nI am the second line");
}

LogStatus(Msg) supports printing base64 encoded pictures, starting with `and ending up with`, such as: LogStatus("`data:image/png;base64,AAAA`"). LogStatus(Msg) supports direct import of Python's matplotlib.pyplot object, as long as the object contains the savefig method, you can pass in the function LogStatus(Msg), such as:

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

The data output example in the status bar:

function main() {
    var table = {type: 'table', title: 'Position Information', cols: ['Column1', 'Column2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}
    // After the JSON order is serialized, add the character "`" on both sides, which is regarded as a complex message format (currently supporting tables)
    LogStatus('`' + JSON.stringify(table) + '`')                    
    // Table information can also appear in multiple lines
    LogStatus('First line message\n`' + JSON.stringify(table) + '`\nThird line message')
    // That supports multiple tables displayed at the same time, and that will be displayed in a group with TAB
    LogStatus('`' + JSON.stringify([table, table]) + '`')
    
    // You can also construct a button in the table, and the strategy uses "GetCommand" to receive the content of the cmd attribute                                
    var table = { 
        type: 'table', 
        title: 'Position operation', 
        cols: ['Column1', 'Column2', 'Action'], 
        rows: [ 
            ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'close position'}]
        ]
    }
    LogStatus('`' + JSON.stringify(table) + '`') 
    // Or create a separate button 
    LogStatus('`' + JSON.stringify({'type':'button', 'cmd': 'coverAll', 'name': 'close position'}) + '`') 
    // You can customize the button style (button attribute of bootstrap)
    LogStatus('`' + JSON.stringify({'type':'button', 'class': 'btn btn-xs btn-danger', 'cmd': 'coverAll', 'name': 'close position'}) + '`')
}
import json
def main():
    table = {"type": "table", "title": "Position Information", "cols": ["Column1", "Column2"], "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" : ["Column1", "Column2", "Action"], 
        "rows" : [
            ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}]
        ] 
    }
    LogStatus('`' + json.dumps(table) + '`')
    LogStatus('`' + json.dumps({"type": "button", "cmd": "coverAll", "name": "close position"}) + '`')
    LogStatus('`' + json.dumps({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "close position"}) + '`')
void main() {
    json table = R"({"type": "table", "title": "Position Information", "cols": ["Column1", "Column2"], "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" : ["Column1", "Column2", "Action"], 
        "rows" : [
            ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}]
        ] 
    })"_json;
    LogStatus("`" + table.dump() + "`");
    LogStatus("`" + R"({"type": "button", "cmd": "coverAll", "name": "close position"})"_json.dump() + "`");
    LogStatus("`" + R"({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "close position"})"_json.dump() + "`");
}

Set the disable and description functions of the status bar buttons:

img

function main() {
    var table = {
        type: "table",
        title: "Test the disable and description functions of status bar buttons",
        cols: ["Column1", "Column2", "Column3"], 
        rows: []
    }
    var button1 = {"type": "button", "name": "button1", "cmd": "button1", "description": "This is the first button"}
    var button2 = {"type": "button", "name": "button2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true}
    var button3 = {"type": "button", "name": "button3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false}
    table.rows.push([button1, button2, button3])
    LogStatus("`" + JSON.stringify(table) + "`")
}
import json
def main():
    table = {
        "type": "table",
        "title": "Test the disable and description functions of status bar buttons",
        "cols": ["Column1", "Column2", "Column3"], 
        "rows": []
    }
    button1 = {"type": "button", "name": "button1", "cmd": "button1", "description": "This is the first button"}
    button2 = {"type": "button", "name": "button2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": True}
    button3 = {"type": "button", "name": "button3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": False}
    table["rows"].append([button1, button2, button3])
    LogStatus("`" + json.dumps(table) + "`")
void main() {
    json table = R"({
        "type": "table",
        "title": "Test the disable and description functions of status bar buttons",
        "cols": ["Column1", "Column2", "Column3"], 
        "rows": []
    })"_json;
    json button1 = R"({"type": "button", "name": "button1", "cmd": "button1", "description": "This is the first button"})"_json;
    json button2 = R"({"type": "button", "name": "button2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true})"_json;
    json button3 = R"({"type": "button", "name": "button3", "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() + "`");
}

Set the style of the status bar buttons:

img

function main() {
    var table = {
        type: "table",
        title: "status bar button style",
        cols: ["default", "raw", "success", "information", "warning", "danger"], 
        rows: [
            [
                {"type":"button", "class": "btn btn-xs btn-default", "name": "default"},
                {"type":"button", "class": "btn btn-xs btn-primary", "name": "raw"},
                {"type":"button", "class": "btn btn-xs btn-success", "name": "success"},
                {"type":"button", "class": "btn btn-xs btn-info", "name": "information"},
                {"type":"button", "class": "btn btn-xs btn-warning", "name": "warning"},
                {"type":"button", "class": "btn btn-xs btn-danger", "name": "danger"}
            ]
        ]
    }
    LogStatus("`" + JSON.stringify(table) + "`")
}
import json
def main():
    table = {
        "type": "table",
        "title": "status bar button style",
        "cols": ["default", "raw", "success", "information", "warning", "danger"], 
        "rows": [
            [
                {"type":"button", "class": "btn btn-xs btn-default", "name": "default"},
                {"type":"button", "class": "btn btn-xs btn-primary", "name": "raw"},
                {"type":"button", "class": "btn btn-xs btn-success", "name": "success"},
                {"type":"button", "class": "btn btn-xs btn-info", "name": "information"},
                {"type":"button", "class": "btn btn-xs btn-warning", "name": "warning"},
                {"type":"button", "class": "btn btn-xs btn-danger", "name": "danger"}
            ]
        ]
    }
    LogStatus("`" + json.dumps(table) + "`")
void main() {
    json table = R"({
        "type": "table",
        "title": "status bar button style",
        "cols": ["default", "raw", "success", "information", "warning", "danger"], 
        "rows": [
            [
                {"type":"button", "class": "btn btn-xs btn-default", "name": "default"},
                {"type":"button", "class": "btn btn-xs btn-primary", "name": "raw"},
                {"type":"button", "class": "btn btn-xs btn-success", "name": "success"},
                {"type":"button", "class": "btn btn-xs btn-info", "name": "information"},
                {"type":"button", "class": "btn btn-xs btn-warning", "name": "warning"},
                {"type":"button", "class": "btn btn-xs btn-danger", "name": "danger"}
            ]
        ]
    })"_json;
    LogStatus("`" + table.dump() + "`");
}

Combine the functionGetCommand() to construct the interactive function of the status bar buttons:

function test1() {
    Log("Call a custom function")
}

function main() {
    while (true) {
        var table = {
            type: 'table',
            title: 'operation',
            cols: ['Column1', 'Column2', 'Action'],
            rows: [
                ['a', '1', {
                    'type': 'button',                       
                    'cmd': "CoverAll",                      
                    'name': 'close position'                           
                }],
                ['b', '1', {
                    'type': 'button',
                    'cmd': 10,                              
                    'name': 'Send value'
                }],
                ['c', '1', {
                    'type': 'button',
                    'cmd': _D(),                          
                    'name': 'Call a function'
                }],
                ['d', '1', {
                    'type': 'button',
                    'cmd': 'test1',       
                    'name': 'Call a custom function'
                }]
            ]
        }
        LogStatus(_D(), "\n", '`' + JSON.stringify(table) + '`')

        var str_cmd = GetCommand()
        if (str_cmd) {
            Log("Received interactive data str_cmd:", "Types of:", typeof(str_cmd), "Value:", str_cmd)
            if(str_cmd == "test1") {
                test1()
            }
        }

        Sleep(500)
    }
}
import json
def test1():
    Log("Call a custom function")

def main():
    while True:
        table = {
            "type": "table", 
            "title": "Operation", 
            "cols": ["Column1", "Column2", "Action"],
            "rows": [
                ["a", "1", {
                    "type": "button", 
                    "cmd": "CoverAll",
                    "name": "close position"
                }],
                ["b", "1", {
                    "type": "button",
                    "cmd": 10,
                    "name": "Send value" 
                }], 
                ["c", "1", {
                    "type": "button",
                    "cmd": _D(),
                    "name": "Call a function" 
                }],
                ["d", "1", {
                    "type": "button",
                    "cmd": "test1",
                    "name": "Call a custom function" 
                }]
            ]
        }

        LogStatus(_D(), "\n", "`" + json.dumps(table) + "`")
        str_cmd = GetCommand()
        if str_cmd:
            Log("Received interactive data str_cmd", "Types:", type(str_cmd), "Value:", str_cmd)
            if str_cmd == "test1":
                test1()
        Sleep(500)
void test1() {
    Log("Call a custom function");
}

void main() {
    while(true) {
        json table = R"({
            "type": "table", 
            "title": "Operation", 
            "cols": ["Column1", "Column2", "Action"],
            "rows": [
                ["a", "1", {
                    "type": "button", 
                    "cmd": "CoverAll",
                    "name": "close position"
                }],
                ["b", "1", {
                    "type": "button",
                    "cmd": 10,
                    "name": "Send value" 
                }], 
                ["c", "1", {
                    "type": "button",
                    "cmd": "",
                    "name": "Call a function" 
                }],
                ["d", "1", {
                    "type": "button",
                    "cmd": "test1",
                    "name": "Call a custom function" 
                }]
            ]
        })"_json;
        table["rows"][2][2]["cmd"] = _D();
        LogStatus(_D(), "\n", "`" + table.dump() + "`");
        auto str_cmd = GetCommand();
        if(str_cmd != "") {
            Log("Received interactive data str_cmd", "Type:", typeid(str_cmd).name(), "Value:", str_cmd);
            if(str_cmd == "test1") {
                test1();
            }
        }
        Sleep(500);
    }
}

When constructing a status bar button for interaction, input data is also supported, and the interactive command is ultimately captured by the GetCommand() function. To add an input item to the data structure of a button control in the status bar, for example, add "input": {"name": "Number of opening orders", "type": "number", "defValue": 1} to {"type": "button", "cmd": "open", "name": "open position"}, you can make the button pop up a dialog box with an input box control when clicked (The default value in the input box is 1, which is set by the “defValue” data), and you can enter data to send with the button command. For example, when running the following test code, after clicking the “Open position” button, a dialog box with an input box pops up. After entering 111 in the input box and clicking “OK”, the GetCommand function will capture the message: open:111.

function main() {
    var tbl = {
        type: "table",
        title: "operation",
        cols: ["column 1", "column2"],
        rows: [
            ["Open position operation", {"type": "button", "cmd": "open", "name": "open position", "input": {"name": "number of opening positions", "type": "number", "defValue": 1}}],
            ["Close position operation", {"type": "button", "cmd": "coverAll", "name": "close all positions"}]
        ] 
    }

    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": "operation", 
        "cols": ["column 1", "column 2"],
        "rows": [
            ["Open position operation", {"type": "button", "cmd": "open", "name": "open position", "input": {"name": "number of opening positions", "type": "number", "defValue": 1}}],
            ["Close position operation", {"type": "button", "cmd": "coverAll", "name": "close all positions"}]
        ]
    }

    LogStatus(_D(), "\n", "`" + json.dumps(tbl) + "`")
    while True:
        cmd = GetCommand()
        if cmd:
            Log("cmd:", cmd)
        Sleep(1000)
void main() {
    json tbl = R"({
        "type": "table", 
        "title": "operation", 
        "cols": ["column 1", "column 2"],
        "rows": [
            ["Open position operation", {"type": "button", "cmd": "open", "name": "open position", "input": {"name": "number of opening positions", "type": "number", "defValue": 1}}],
            ["Close position operation", {"type": "button", "cmd": "coverAll", "name": "close all positions"}]
        ]
    })"_json;

    LogStatus(_D(), "\n", "`" + tbl.dump() + "`");
    while(true) {
        auto cmd = GetCommand();
        if(cmd != "") {
            Log("cmd:", cmd);
        }
        Sleep(1000);
    }
}

Combine the cells in the table drawn by the LogStatus(Msg) function:

  • Horizontal merger

    function main() {
        var table = { 
            type: 'table', 
            title: 'position operation', 
            cols: ['Column1', 'Column2', 'Action'], 
            rows: [ 
                ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'close position'}]
            ]
        } 
        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) + '`')
    }
    
    import json
    def main():
        table = {
            "type" : "table",
            "title" : "position operation",
            "cols" : ["Column1", "Column2", "Action"],
            "rows" : [
                ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}]
            ]
        }
        ticker = exchange.GetTicker()
        table["rows"].append([{"body": json.dumps(ticker), "colspan": 2}, "abc"])
        LogStatus("`" + json.dumps(table) + "`")
    
    void main() {
        json table = R"({
            "type" : "table",
            "title" : "position operation",
            "cols" : ["Column1", "Column2", "Action"],
            "rows" : [