ntification of a trading order Price : 1000, // Placing order price; notice this attribute of market orders might be 0 or -1 Amount : 10, // Placing order amount; notice this attribute of market orders might be money amount, not coin number DealAmount : 10, // Executed volume; if the platform interface does not provide this kind of data, probably use 0 to fill in AvgPrice : 1000, // The average executed price; notice some platforms do not provide this data.(in the situation when the data cannot be provided or calculated, the setting of the attribute is 0.) Status : 1, // Order status; refer to the order status in the constants, such as ORDER_STATE_CLOSED Type : 0, // Order type; refer to the order type in the constants, such as ORDER_TYPE_BUY Offset : 0 // The order open and close direction in the order data of cryptocurrency futures;ORDER_OFFSET_OPEN is the open position, while ORDER_OFFSET_CLOSE is the close direction ContractType : “” // This attribute is “” for spot orders, namely null string; The property of futures order is the specific contract code }


##### MarketOrder

Market depth order, that is, ```exchange.GetDepth()``` returns the data structure of the elements in the **Bids** and **Asks** arrays in the data.

```javascript
{
    Price   : 1000,              // Price
    Amount  : 1                  // Volume
}
Depth

The market depth is returned by the function exchange.GetDepth().

{
    Asks    : [...],             // The array of sell orders, namely MarketOrder array, sorted by price from low to high
    Bids    : [...],             // The array of buy orders, namely MarketOrder array, sorted by price from high to low
    Time    : 1567736576000      // Millisecond-level timestamp
}
Account

Account information, returned by the function exchange.GetAccount(). The data returned in the structure is related to the currently set trading pairs and contract codes.

{
    Info            : {...},     // After requesting the platform interface, this attribute is not available in the raw data that the platform interface responds to, during the backtest
    Balance         : 1000,      // The available amount of quote currency; if the trading pair is BTC_USDT in the spot trading, "balance" refers to the current USDT amount. In the USDT-margined futures contract, "balance" refers to the available margin amount in USDT
    FrozenBalance   : 0,         // Here "balance" refers to the frozen amount of the assets for pending orders
    Stocks          : 1,         // The available amount of base currency; if the trading pair is BTC_USDT in the spot trading, "stocks" refers to the current BTC amount. In the crypto-margined futures contract, "stocks" refers to the available margin amount (base currency)
    FrozenStocks    : 0          // Here "stocks" refers to the frozen amount of the assets for pending orders
}
Position

For the information about positions held in the futures trading, the array of this Position structure is returned by the function exchange.GetPosition() function.

{
    Info            : {...},     // After requesting the platform interface, this attribute is not available in the raw data that the platform interface responds to, during the backtest
    MarginLevel     : 10,        // The leverage size of positions; if this data is not provided by the platform interface, fill in the data by calculation, possibly with errors
    Amount          : 100,       // Position volume; the contract quantity of positions is normally positive integer. Notice every platform might have different contract specifications, such as contract multiplier and value, etc., so the rules for ordering might be different; for example, Binance contract might order by 0.1
    FrozenAmount    : 0,         // The quantity of frozen positions, used as the number of temporary position freeze when close positions and pending orders
    Price           : 10000,     // Average position price; in principle, the attribute is the average price of the entire position (which does not involve in the settlement); if the platform interface does not provide the data, use the existing average position price of the platform to fill in (which involves in the settlement)
    Profit          : 0,         // Position floating profit and loss, namely the failure of realizing position profit and loss. If the platform interface does not provide the data, use other profit and loss data from the interface to fill in; the unit of the profit and loss values is the same as the unit of the current contract margin 

    Type            : 0,         // PD_LONG is a long position, while PD_SHORT is a short position
    ContractType    : "quarter", // Contract code; for more details, refer to the transmitted parameters in the description of the function "SetContractType"
    Margin          : 1          // Margin occupied by positions; if the platform interface does not provide the data, use 0 to fill in
}

For the cryptocurrency futures, pay attention to the Position structure array returned by the function exchange.GetPosition(). As for the attributes in its position data structure, such as FrozenAmount, Profit and Margin, different data definitions might be returned by different exchange objects, when they call exchange.GetPosition() interface, for different platforms provide different data. For instance, some exchanges do not include position frozen data, which indicates FrozenAmount is 0. If in need of calculating some data, you can use the source data in the attribute Info for calculation and analysis.

Global Functions

Version()

Version() returns the current version number of the system; return value: string type.

Sleep(Millisecond)

Sleep(Millisecond), sleep function, makes the program pause for a period of time. Parameter value: Millisecond is a number type. The parameter unit is millisecond, for example: Sleep(1000) means to sleep for one second. Support operations with sleep time less than 1 millisecond, such as setting Sleep (0.1). The minimum parameter supported is 0.000001, nanosecond sleep. One nanosecond is equal to 1e-6 milliseconds.

Note: When you write strategies in Python language, the function Sleep(Millisecond) should be used for operations, such as polling interval and time waiting. It is not recommended to use the function time.time(second) of the time library in Python. Because when using the function time.time(second) in the strategy, the strategy program will actually wait for a certain number of seconds (the parameter second is the second number of the pause setting), which will lead to a very slow strategy backtest.

IsVirtual()

IsVirtual(), to determine whether it is a simulated backtest. Return value: bool type. The simulated backtest status returns true, and the real bot returns false.

Mail(…)

Mail(smtpServer, smtpUsername, smtpPassword, mailTo, title, body) is a mail sending function. Parameter value: all are of string type. Return value: bool type; true is returned after a successful sending. smtpServer serves for the sending mailbox smtp; smtpUsername is the mailbox account; smtpPassword is the STMP password of the mailbox; mailTo is the receiving mailbox account; title is the title of the mail sent; body is the content of the mail sent, e.g.:

function main(){
    Mail("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
}
def main():
    Mail("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
void main() {
    Mail("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body");
}
  • The Mail_Go function is an asynchronous version of function Mail: Its usage is similar to the function exchange.Go.

    function main() {
        var r1 = Mail_Go("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
        var r2 = Mail_Go("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
        
        var ret1 = r1.wait()
        var ret2 = r2.wait()
        
        Log("ret1:", ret1)
        Log("ret2:", ret2)
    }
    
    # Not supported
    
    // Not supported
    

Note: Alibaba Cloud server may block some ports, so that the mail cannot be sent. If you need to change the port, you can add the port number in the first parameter directly, for example, smtp.qq.com:587 (QQmail), the port is available for testing. If an error occurs: unencrypted connection, you need to modify the parameter format of smtpServer in the function Mail to: ssl://xxxxx.com:xxx. For example, the SMTP ssl method of QQmail: ssl://smtp.qq.com:465 or smtp://xxxxx.com:xxx.

SetErrorFilter(…)

ErrorFilter(RegEx), filtering error logs. Parameter value: string type. Errors matched by this regular expression will not be uploaded to the log system, and it can be called multiple times (filtered logs will not be written to the database file of the corresponding bot ID in logs/bots in the docker content, to prevent the database file expansion caused by frequent error reporting).

function main() {
    SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
}
def main():
    SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
void main() {
    SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused");
}

Filter the error information of an interface:

function main() {
    // Randomly check a non-existent order with ID 123; intentionally make the interface report an error
    var order = exchange.GetOrder("123")
    Log(order)
    // Filter http502 errors and GetOrder interface errors; after set the error filter, the second time to call GetOrder no longer reports errors
    SetErrorFilter("502:|GetOrder")
    order = exchange.GetOrder("123")
    Log(order)
}
def main():
    order = exchange.GetOrder("123")
    Log(order)
    SetErrorFilter("502:|GetOrder")
    order = exchange.GetOrder("123")
    Log(order)
void main() {
    TId orderId;
    Order order = exchange.GetOrder(orderId);
    Log(order);
    SetErrorFilter("502:|GetOrder");
    order = exchange.GetOrder(orderId);
    Log(order);
}

GetPid()

GetPid() returns the bot process ID. Return value: string type.

function main(){
    var id = GetPid()
    Log(id)
}
def main():
    id = GetPid()
    Log(id)
void main() {
    auto id = GetPid();
    Log(id);
}

GetLastError()

GetLastError() obtains the latest error information; generally, it does not need to be used, because the program will upload the error information to the log system automatically. Return value: string type. After you called the function GetLastError(), the error cache will be cleared; when it is called again, the error information recorded last time will not be returned.

function main(){
    // Because the order with ID number of 123 does not exist, an error is reported
    exchange.GetOrder("123")
    var error = GetLastError()
    Log(error)
}
def main():
    exchange.GetOrder("123")
    error = GetLastError()
    Log(error)
void main() {
    // The type of order ID: TId; so no string can be passed in, and we can trigger it by placing an order that does not meet the exchange specifications
    exchange.GetOrder(exchange.Buy(1, 1));
    auto error = GetLastError();
    Log(error);
}

GetCommand()

GetCommand() obtains interactive commands (utf-8). It gets the command sent by the strategy interactive interface and clear the cache; if there is no command, it returns an empty string. The command format returned is button name: parameter; if there is no parameter in the interactive controls (for example, the button control without input box), the command is the button name.

function main(){
    while(true) { 
        var cmd = GetCommand()
        if (cmd) { 
            Log(cmd)
        }
        Sleep(1000) 
    }
}
def main():
    while True:
        cmd = GetCommand()
        if cmd:
            Log(cmd)
        Sleep(1000)
void main() {
    while(true) {
        auto cmd = GetCommand();
        if(cmd != "") {
            Log(cmd);
        }
        Sleep(1000);
    }
}

The underlying system has a queue structure to record the interactive command. When the function GetCommand() is called, the interactive command that first enter in the queue will be taken out (if there is no interactive command, an empty string).

Examples of using interactive controls; set interactive controls on the strategy editing interface:

img

Design interactive codes in the strategy:

function main() {
    while (true) {
        LogStatus(_D())
        var cmd = GetCommand()
        if (cmd) {
            Log("cmd:", cmd)    
            var arr = cmd.split(":")
            if (arr[0] == "buy") {
                Log("Buy, the control without quantity")
            } else if (arr[0] == "sell") {
                Log("Sell, the control with quantity: ", arr[1])
            } else {
                Log("Other controls trigger:", arr)
            }
        }
        Sleep(1000)
    } 
}
def main():
    while True:
        LogStatus(_D())
        cmd = GetCommand()
        if cmd:
            Log("cmd:", cmd)
            arr = cmd.split(":")
            if arr[0] == "buy":
                Log("Buy, the control without quantity")
            elif arr[0] == "sell":
                Log("Sell, the control with quantity:", arr[1])
            else:
                Log("Other controls trigger:", arr)
        Sleep(1000)
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
void split(const string& s,vector<string>& sv,const char flag = ' ') {
    sv.clear();
    istringstream iss(s);
    string temp;

    while (getline(iss, temp, flag)) {
        sv.push_back(temp);
    }
    return;
}

void main() {
    while(true) {
        LogStatus(_D());
        auto cmd = GetCommand();
        if (cmd != "") {
            vector<string> arr;
            split(cmd, arr, ':');
            if(arr[0] == "buy") {
                Log("Buy, the control without quantity");
            } else if (arr[0] == "sell") {
                Log("Sell, the control with quantity:", arr[1]);
            } else {
                Log("Other controls trigger:", arr);
            }
        }
        Sleep(1000);
    }
}

GetMeta()

The function GetMeta() returns the value of Meta written when the strategy token is generated, the return value of the function is of string type. Applications: for example, the strategy needs to make assets limitations for different tenants. Note: when the strategy token is generated, the length of Meta cannot exceed 190 strings; the function, only applicable to actual trading, and requires the newest docker. If the source data is not set when the strategy token is generated, GetMeta() returns null.

the related information demonstrated by applications

function main() {
    // The largest assets value of quote currency allowed by the strategy
    
    // Get the metadata generated when the strategy token is established
    var level = GetMeta()
    
    // Check the corresponding conditions of "Meta"
    if (level == "level1") {
        // "-1" indicates no limitation 
        maxBaseCurrency = -1       
    } else if (level == "level2") {
        maxBaseCurrency = 10     
    } else if (level == "level3") {
        maxBaseCurrency = 1
    } else {
        maxBaseCurrency = 0.5
    }
    
    while(1) {
        Sleep(1000)
        var ticker = exchange.GetTicker()
        
        // Check the assets value
        var acc = exchange.GetAccount()
        if (maxBaseCurrency != -1 && maxBaseCurrency < acc.Stocks + acc.FrozenStocks) {
            // Stop the execution of the strategy trading logic 
            LogStatus(_D(), "level:", level, "The position exceeds the strategy token's using limitation, and stop the execution of the strategy trading logic!")
            continue
        }
        
        // Other trading logic
        
        // Export the information of status bar normally
        LogStatus(_D(), "level:", level, "The strategy runs normally! Ticker data: \n", ticker)
    }
}
def main():
    maxBaseCurrency = null
    level = GetMeta()
    
    if level == "level1":
        maxBaseCurrency = -1       
    elif level == "level2":
        maxBaseCurrency = 10     
    elif level == "level3":
        maxBaseCurrency = 1
    else:
        maxBaseCurrency = 0.5
    
    while True:
        Sleep(1000)
        ticker = exchange.GetTicker()        
        acc = exchange.GetAccount()
        if maxBaseCurrency != -1 and maxBaseCurrency < acc["Stocks"] + acc["FrozenStocks"]:
            LogStatus(_D(), "level:", level, "The position exceeds the strategy token's using limitation, and stop the execution of the strategy trading logic!")
            continue        
        
        # Other trading logic
        
        # Export the information of status bar normally
        LogStatus(_D(), "level:", level, "The strategy runs normally! Ticker data: \n", ticker)
void main() {
    auto maxBaseCurrency = 0.0;
    auto level = GetMeta();
    
    if (level == "level1") {
        maxBaseCurrency = -1;  
    } else if (level == "level2") {
        maxBaseCurrency = 10;
    } else if (level == "level3") {
        maxBaseCurrency = 1;
    } else {
        maxBaseCurrency = 0.5;
    }
    
    while(1) {
        Sleep(1000);
        auto ticker = exchange.GetTicker();  
        auto acc = exchange.GetAccount();
        if (maxBaseCurrency != -1 && maxBaseCurrency < acc.Stocks + acc.FrozenStocks) {
            // Stop the execution of the strategy trading logic
            LogStatus(_D(), "level:", level, "The position exceeds the strategy token's using limitation, and stop the execution of the strategy trading logic!");
            continue;
        }
        
        // Other trading logic
        
        // Export the information of status bar normally
        LogStatus(_D(), "level:", level, "The strategy runs normally! Ticker data: \n", ticker);
    }
}

Dial(…)

Dial(Address, Timeout), original Socket access, supports tcp, udp, tls and unix protocols. Parameter value: Address is a string type; unit is second; if the time is out, the function Dial(...) returns an empty value.

Detailed description ofAddress parameter:

Parameter Details
Parameters for setting the function Dial Separate by adding the | symbol after the normal address: wss://ws.okx.com:8443/ws/v5/public; if there are | characters in the parameter string, use || as the delimiter. Connect each parameter with &; for example, ss5 proxy and compression parameters are set together: Dial("wss://ws.okx.com:8443/ws/v5/public|proxy=socks5://xxx:9999&compress=gzip_raw&mode=recv").
In the ws protocol, related parameters of data compression: compress=parameter value compress is the compression method; compress parameters can be chosen from gzip_raw and gzip, etc. If the gzip method is non-standard gzip, you can use the extension method: gzip_raw, that is, add the setting compress=gzip_raw after the separator |, and use the & symbol and the next mode parameter to separate.
In the ws protocol, related parameters of data compression: mode=parameter value mode is the mode, including three options, namely dual, send and recv. dual is bidirectional, sending and receiving compressed data. send is to send compressed data. recv is to receive compressed data and decompress it locally.
Related parameters for setting socks5 proxy: proxy=parameter value proxy is ss5 proxy setting; parameter value format: socks5://name:pwd@192.168.0.1:1080; “name” is ss5 server user name; “pwd” is ss5 server login password; “1080” is the port of ss5 server
In the ws protocol, related parameters for setting the underlying auto-reconnect: reconnect=parameter value reconnect means whether to set reconnect; reconnect=true is to invoke re-connection; the default setting is not to reconnect.
In the ws protocol, related parameters for setting the underlying auto-reconnect: interval=parameter value interval is the retry interval in millisecond, interval=10000 is the retry interval of 10 seconds, and the default setting is 1 second, that is, interval=1000.
In the ws protocol, related parameters for setting the underlying auto-reconnect: payload= parameter value payload is the subscription message to be sent when ws reconnects, for example: payload=okok.
function main(){
    // Dial supports tcp://, udp://, tls://, unix:// protocol, so you can add a parameter to specify the number of seconds to timeout
    var client = Dial("tls://www.baidu.com:443")  
    if (client) {
        // "Write" can be followed by a numeric parameter to specify the timeout, and "write" returns the number of bytes successfully sent
        client.write("GET / HTTP/1.1\nConnection: Closed\n\n")
        while (true) {
            // "Read" can be followed by a numeric parameter to specify the timeout, in millisecond. Return null to indicate error, timeout or closed 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();
    }
}

The function read supports the following parameters:

  • When no parameters are passed, it blocks until there is a message, such as ws.read().
  • When passing in parameters, specify the timeout for waiting messages in the unit of millisecond; for example: ws.read(2000) specifies the timeout to be two seconds (2000 milliseconds).
  • The following two parameters are only valid for websocket: Passing in parameter -1 means to return immediately regardless of whether there is any message, such as ws.read(-1). Passing in parameter -2 means to return immediately regardless of whether there is any message, but only the latest message is returned, and the message in the buffer will be discarded, such as ws.read(-2).

The function read() buffer description: If the data pushed by the ws protocol is of long intervals between the strategy read() function calls, it may cause data accumulation, and these data are stored in the buffer. The buffer data structure is a queue, with an upper limit of 2000. After exceeding 2000, the latest data enters the buffer, and the oldest data is cleared.

Scenarios \ Read Function parameter No parameter Parameter: -1 Parameter: -2 Parameter: 2000 (unit: ms)
Buffer already has data Return the oldest data immediately Return the oldest data immediately Return the latest data immediately Return the oldest data immediately
No data in the buffer Return the data when there is data blocked Return null immediately Return null immediately Wait for 2000ms, return null if there is no data, return the data if there is data
Ws connection is disconnected or the bottom layer is reconnected The “read()” function returns null, that is: “”, and the function “write()” returns 0. If this situation is detected, the function “close()” can be used to close the connection. If set reconnection automatically, it won’t need to close, and the bottom layer of the system will automatically reconnect
  • Support wss (WebSocket) protocol Access Binance websocket market interface:

    function main() {
        LogStatus("connecting...")
        // Access Binance websocket interface
        var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr")
        if (!client) {
            Log("connection failed, program exited")
            return
        }
        
        while (true) {
            // "read" only returns the obtained data after call "read" 
            var buf = client.read()      
            if (!buf) {
                break
            }
            var table = {
                type: 'table',
                title: 'Quote Chart',
                cols: ['Currency', 'Highest', 'Lowest', 'Buy One', 'Sell One', 'Last Executed Price', 'Volume', 'Update Time'],
                rows: []
            }
            var obj = JSON.parse(buf)
            _.each(obj, function(ticker) {
                table.rows.push([ticker.s, ticker.h, ticker.l, ticker.b, ticker.a, ticker.c, ticker.q, _D(ticker.E)])
            })
            LogStatus('`' + JSON.stringify(table) + '`')
        }
        client.close()
    }
    
    import json
    def main():
        LogStatus("connecting...")
        client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr")
        if not client:
            Log("Connection failed, program exited")
            return 
        
        while True:
            buf = client.read()
            if not buf:
                break
            table = {
                "type" : "table", 
                "title" : "Quote Chart", 
                "cols" : ['Currency', 'Highest', 'Lowest', 'Buy One', 'Sell One', 'Last Executed Price', 'Volume', 'Update Time'], 
                "rows" : [] 
            }
            obj = json.loads(buf)
            for i in range(len(obj)):
                table["rows"].append([obj[i]["s"], obj[i]["h"], obj[i]["l"], obj[i]["b"], obj[i]["a"], obj[i]["c"], obj[i]["q"], _D(int(obj[i]["E"]))])
            LogStatus('`' + json.dumps(table) + '`')
        client.close()
    
    void main() {
        LogStatus("connecting...");
        auto client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr");
        if(!client.Valid) {
            Log("Connection failed, program exited");
            return;
        }
        
        while(true) {
            auto buf = client.read();
            if(buf == "") {
                break;
            }
            json table = R"({
                "type" : "table", 
                "title" : "Quote Chart", 
                "cols" : ['Currency', 'Highest', 'Lowest', 'Buy One', 'Sell One', 'Last Executed Price', 'Volume', 'Update Time'], 
                "rows" : []
            })"_json;
            json obj = json::parse(buf);
            for(auto& ele : obj.items()) {
                table["rows"].push_back({ele.value()["s"], ele.value()["h"], ele.value()["l"], ele.value()["b"], ele.value()["a"], ele.value()["c"], 
                    ele.value()["q"], _D(ele.value()["E"])});
            }
            LogStatus("`" + table.dump() + "`");
        }
        client.close();
    }
    

    Access OKX websocket market interface:

    var ws = null 
    function main(){
          var param = {
            "op": "subscribe",
            "args": [{
                "channel": "tickers",
                "instId": "BTC-USDT"
            }]
        }
        // When call the function "Dial", specify "reconnect=true" and set to reconnect; specify "payload" as the message to be sent when reconnect. When websocket closes the connection, it will automatically reconnect and send the message
        ws = Dial("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true&payload="+ JSON.stringify(param))
        if(ws){
            ws.write(JSON.stringify(param))
            var pingCyc = 1000 * 20
            var lastPingTime = new Date().getTime()
            while(true){
                var nowTime = new Date().getTime()
                var ret = ws.read()
                Log("ret:", ret)
                if(nowTime - lastPingTime > pingCyc){
                    var retPing = ws.write("ping")
                    lastPingTime = nowTime
                    Log("send: ping", "#FF0000")
                }
                LogStatus("current time:", _D())
                Sleep(1000)
            }
        }
    }
    
    function onexit() {
        ws.close() 
        Log("exit")
    }
    
    import json
    import time
    
    ws = None
    def main():
        global ws 
        param = {
            "op": "subscribe",
            "args": [{
                "channel": "tickers",
                "instId": "BTC-USDT"
            }]
        }
        ws = Dial("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true&payload=" + json.dumps(param))
        if ws:
            pingCyc = 1000 * 20
            lastPingTime = time.time() * 1000
            while True:
                nowTime = time.time() * 1000
                ret = ws.read()
                Log("ret:", ret)
                if nowTime - lastPingTime > pingCyc:
                    retPing = ws.write("ping")
                    lastPingTime = nowTime
                    Log("send: ping", "#FF0000")
                LogStatus("current time:", _D())
                Sleep(1000)
    
    def onexit():
        ws.close()
        Log("exit")
    
    auto objWS = Dial("wss://ws.okx.com:8443/ws/v5/public|compress=gzip_raw&mode=recv&reconnect=true");  
    
    void main() {
            json param = R"({"op": "subscribe", "args": ["spot/ticker:ETH-USDT"]})"_json;
             "op": "subscribe",
            "args": [{
                "channel": "tickers",
                "instId": "BTC-USDT"
            }]
        })"_json;
        
        objWS.write(param.dump());
        if(objWS.Valid) {
            uint64_t pingCyc = 1000 * 20;
            uint64_t lastPingTime = Unix() * 1000;
            while(true) {
                uint64_t nowTime = Unix() * 1000;
                auto ret = objWS.read();
                Log("ret:", ret);
                if(nowTime - lastPingTime > pingCyc) {
                    auto retPing = objWS.write("ping");
                    lastPingTime = nowTime;
                    Log("send: ping", "#FF0000");
                }
                LogStatus("current time:", _D());
                Sleep(1000);
            }
        }
    }  
    
    void onexit() {
        objWS.close();
        Log("exit");
    }
    

    Access Huobi websocket market interface:

    var ws = null   
    
    function main(){
        var param = {"sub": "market.btcusdt.detail", "id": "id1"}
        ws = Dial("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload="+ JSON.stringify(param))
        if(ws){
            while(1){
                var ret = ws.read()
                Log("ret:", ret)
                // Respond to heartbeat
                try {
                    var jsonRet = JSON.parse(ret)
                    if(typeof(jsonRet.ping) == "number") {
                        var strPong = JSON.stringify({"pong" : jsonRet.ping})
                        ws.write(strPong)
                        Log("respond ping, send pong:", strPong, "#FF0000")
                    }
                } catch(e) {
                    Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)
                }
                
                LogStatus("current time:", _D())
                Sleep(1000)
            }
        }
    }  
    
    function onexit() {
        ws.close() 
        Log("execute function ws.close()")
    }
    
    import json
    ws = None  
    
    def main():
        global ws
        param = {"sub" : "market.btcusdt.detail", "id" : "id1"}
        ws = Dial("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload=" + json.dumps(param))
        if ws:
            while True:
                ret = ws.read()
                Log("ret:", ret)              
                # Respond to heartbeat 
                try:
                    jsonRet = json.loads(ret)
                    if "ping" in jsonRet and type(jsonRet["ping"]) == int:
                        strPong = json.dumps({"pong" : jsonRet["ping"]})
                        ws.write(strPong)
                        Log("respond ping, send pong:", strPong, "#FF0000")
                except Exception as e:
                    Log("e:", e)
                    
                LogStatus("current time: ", _D())
                Sleep(1000)
        
    def onexit():
        ws.close()
        Log("execute function ws.close()")  
    
    using namespace std;
    void main() {
        json param = R"({"sub" : "market.btcusdt.detail", "id" : "id1"})"_json;
        auto ws = Dial("wss://api.huobi.pro/ws|compress=gzip&mode=recv&reconnect=true&payload=" + param.dump());
        if(ws.Valid) {
            while(true) {
                auto ret = ws.read();
                Log("ret:", ret);              
                // Respond to heartbeat
                try 
                {
                    auto jsonRet = json::parse(ret);
                    if(jsonRet["ping"].is_number()) {
                        json pong = R"({"pong" : 0})"_json;
                        pong["pong"] = jsonRet["ping"];
                        auto strPong = pong.dump();
                        ws.write(strPong);
                        Log("respond ping, send pong:", strPong, "#FF0000");
                    }
                } catch(exception &e) 
                {
                    Log("e:", e.what());
                }
                
                LogStatus("current time:", _D());
                Sleep(1000);
            }
        }
    }  
    
    void onexit() {
        // ws.close();
        Log("execute function ws.close()");
    }
    

    Verification interface of websocket interface accessing OKX:

      function getLogin(pAccessKey, pSecretKey, pPassphrase) {
        // Signature function for login
        var ts = (new Date().getTime() / 1000).toString()
        var login = {
            "op": "login",
            "args":[{
                "apiKey"    : pAccessKey,
                "passphrase" : pPassphrase,
                "timestamp" : ts,
                "sign" : exchange.HMAC("sha256", "base64", ts + "GET" + "/users/self/verify", pSecretKey)
            }]
        }    
        return login
    }    
    
    var client_private = null 
    function main() {
        // Because the read function adopts the timeout setting, the timeout error is filtered, otherwise there will be redundant error output
        SetErrorFilter("timeout")
        
        // Position channel subscription information
        var posSubscribe = {
            "op": "subscribe",
            "args": [{
                "channel": "positions",
                "instType": "ANY"
            }]
        }    
    
        var accessKey = "xxx"
        var secretKey = "xxx"
        var passphrase = "xxx"
    
        client_private = Dial("wss://ws.okx.com:8443/ws/v5/private")
        client_private.write(JSON.stringify(getLogin(accessKey, secretKey, passphrase)))
        Sleep(3000)  // When logging in, we cannot subscribe to private channels immediately, we need to wait for server response
        client_private.write(JSON.stringify(posSubscribe))
        if (client_private) {
            var lastPingTS = new Date().getTime()
            while (true) {
                var buf = client_private.read(-1)
                if (buf) {
                    Log(buf)
                }
                
                // Detect disconnection, reconnect
                if (buf == "" && client_private.write(JSON.stringify(posSubscribe)) == 0) {
                    Log("Disconnection detected, connection closed, reconnect")
                    client_private.close()
                    client_private = Dial("wss://ws.okx.com:8443/ws/v5/private")
                    client_private.write(JSON.stringify(getLogin(accessKey, secretKey, passphrase)))
                    Sleep(3000)
                    client_private.write(JSON.stringify(posSubscribe))
                }
                
                // Send heartbeat packets
                var nowPingTS = new Date().getTime()
                if (nowPingTS - lastPingTS > 10 * 1000) {
                    client_private.write("ping")
                    lastPingTS = nowPingTS
                }            
            }        
        }
    }    
    
    function onexit() {    
        var ret = client_private.close()
        Log("Close the connection!", ret)
    }
    
    import json
    import time
        
    def getLogin(pAccessKey, pSecretKey, pPassphrase):
        ts = str(time.time())
        login = {
            "op": "login",
            "args":[{
                "apiKey"    : pAccessKey,
                "passphrase" : pPassphrase,
                "timestamp" : ts,
                "sign" : exchange.HMAC("sha256", "base64", ts + "GET" + "/users/self/verify", pSecretKey)
            }]
        }
        return login     
    
    client_private = None 
    def main():
        global client_private
        SetErrorFilter("timeout")
        
        posSubscribe = {
            "op": "subscribe",
            "args": [{
                "channel": "positions",
                "instType": "ANY"
            }]
        }      
    
        accessKey = "xxx"
        secretKey = "xxx"
        passphrase = "xxx"
        
        client_private = Dial("wss://ws.okx.com:8443/ws/v5/private")
        client_private.write(json.dumps(getLogin(accessKey, secretKey, passphrase)))
        Sleep(3000)
        client_private.write(json.dumps(posSubscribe))
        if client_private:
            lastPingTS = time.time() * 1000
            while True:
                buf = client_private.read(-1)
                if buf:
                    Log(buf)
                
                if buf == "" and client_private.write(json.dumps(posSubscribe)) == 0:
                    Log("Disconnection detected, connection closed, reconnect")
                    ret = client_private.close()
                    client_private = Dial("wss://ws.okx.com:8443/ws/v5/private")
                    client_private.write(json.dumps(getLogin(accessKey, secretKey, passphrase)))
                    Sleep(3000)
                    client_private.write(json.dumps(posSubscribe))
                
                nowPingTS = time.time() * 1000
                if nowPingTS - lastPingTS > 10 * 1000:
                    client_private.write("ping")
                    lastPingTS = nowPingTS    
    
    def onexit():
        ret = client_private.close()
        Log("Close the connection!", ret)
    

auto client_private = Dial(“wss://ws.okx.com:8443/ws/v5/private”);

json getLogin(string pAccessKey, string pSecretKey, string pPassphrase) { auto ts = std::to_string(Unix()); json login = R"({ “op”: “login”, “args”: [{ “apiKey”: “”, “passphrase”: “”, “timestamp”: “”, “sign”: “” }] })"_json; login[“args”][0][“apiKey”] = pAccessKey; login[“args”][0][“passphrase”] = pPassphrase; login[“args”][0][“timestamp”] = ts; login[“args”][0][“sign”] = exchange.HMAC(“sha256”, “base64”, ts + “GET” + “/users/self/verify”, pSecretKey); return login; }

void main() { SetErrorFilter(“timeout”); json posSubscribe = R"({ “op”: “subscribe”, “args”: [{ “channel”: “positions”, “instType”: “ANY” }] })"_json;

auto accessKey = "xxx";
auto secretKey = "xxx";
auto passphrase = "xxx";

client_private.write(getLogin(accessKey, secretKey, passphrase).dump());
Sleep(3000);
client_private.write(posSubscribe.dump());  

if (client_private.Valid) {
    uint64_t lastPingTS = Unix() * 1000;    

    while (true) {
        auto buf = client_private.read(-1);
        if (buf != "") {
            Log(buf);
        }
        if (buf == "") {
            if (client_private.write(posSubscribe.dump()) == 0) {
                Log("Disconnection detected, connection closed, reconnect");
                client_private.close();
                client_private = Dial("wss://ws.okx.com:8443/ws/v5/private");
                client_private.write(getLogin(accessKey, secretKey, passphrase).dump());
                Sleep(3000);
                client_private.write(posSubscribe.dump());
            }
        }
        
        uint64_t nowPingTS = Unix() * 1000;
        if (nowPingTS - lastPingTS > 10 * 1000) {
            client_private.write("ping");
            lastPingTS = nowPingTS;
        }
    }
}

}

void onexit() { client_private.close(); Log(“exit”); }


#### HttpQuery(...)

```HttpQuery(Url, PostData, Cookies, Headers, IsReturnHeader)``` is an access of web URL. Parameter value: all are of string types.

Note:
* The ```HttpQuery(...)``` function only supports ```JavaScript``` language.
* For the ```Python``` language, you can use ```urllib``` to send http requests directly.

```HttpQuery(...)``` is mainly used to access the exchange interfaces that do not require signatures, such as public interfaces including market information.
An example of an API that does not require a signature to access OKX: the return value is a ```JSON``` string, which can be parsed by using the function ```JSON.parse()``` in JavaScript language strategies.

```js
function main(){
  // An example of GET access without parameters
  var info = JSON.parse(HttpQuery("https://www.okx.com/api/v5/public/time"))
  Log(info)
  // An example of GET access with parameters
  var ticker = JSON.parse(HttpQuery("https://www.okx.com/api/v5/market/books?instId=BTC-USDT"))
  Log(ticker)
}
import json
import urllib.request
def main():
    # HttpQuery does not support Python, you can use urllib/urllib2 instead
    info = json.loads(urllib.request.urlopen("https://www.okx.com/api/v5/public/time").read().decode('utf-8'))
    Log(info)
    ticker = json.loads(urllib.request.urlopen("https://www.okx.com/api/v5/market/books?instId=BTC-USDT").read().decode('utf-8'))
    Log(ticker)
void main() {
    auto info = json::parse(HttpQuery("https://www.okx.com/api/v5/public/time"));
    Log(info);
    auto ticker = json::parse(HttpQuery("https://www.okx.com/api/v5/market/books?instId=BTC-USDT"));
    Log(ticker);
}

To get the return content of a URL, if the second parameter PostData is in the form of string a=1&b=2&c=abc, submit by POST, others by PUT; the parameter of PostData is {method:'PUT', data:'a=1&b=2&c=abc'}.

The PostData parameter can also be a JSON string.

The format of the parameter Cookies is: a=10; b=20; with each parameter separated by a semicolon ;. The format of the parameter Headers is: User-Agent: Mobile\nContent-Type: text/html; with each parameter separated by a newline character \n.

The second parameter, PostData, can be customized, for example: HttpQuery("http://www.abc.com", {method:'PUT', data:'a=1&b=2&c=abc'}); note: if you need to set the timeout for the HttpQuery function, you can add the timeout attribute in {method:'put',data:'a=1&B=2&C=ABC'} (the default is 60 seconds).

Set 1 second timeout: HttpQuery("http://www.abc.com", {method:'PUT', data:'a=1&b=2&c=abc', timeout:1000})

The third parameter is required to pass the string Cookie, but POST is not required to set the second parameter to null. During the simulation test, because the URL cannot be simulated, the function returns a fixed string Dummy Data. You can use this interface to send text messages or interact with other API interfaces.

GET method call example: HttpQuery("http://www.baidu.com"). POST method call example: HttpQuery("http://www.163.com", "a=1&b=2&c=abc").

Invocation example of returning 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}
  • The function HttpQuery uses proxy settings:

    function main() {
        // This time, set proxy and send http request; without username and password, this http request will be sent through the proxy
        HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/")
    
        // Set the proxy and send http request this time, enter the user name and password, only the current call of HttpQuery takes effect, then call HttpQuery ("http://www.baidu.com") again so that the proxy will not be used
        HttpQuery("socks5://username:password@127.0.0.1:8889/http://www.baidu.com/")
    }
    
    # If HttpQuery does not support Python, you can use Python urllib2 library
    
    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/");
    }
    
  • The function HttpQuery's asynchronous version HttpQuery_Go: The using method is similar to the function exchange.Go, such as accessing the public interface of the exchange asynchronously to obtain aggregated market data.

    function main() {
        // Set up the first asyncthread
        var r1 = HttpQuery_Go("https://www.okx.com/api/v5/market/tickers?instType=SPOT")
        // Set up the second asyncthread
        var r2 = HttpQuery_Go("https://api.huobi.pro/market/tickers")
        
        // Get the return value of the first asyncthread
        var tickers1 = r1.wait()
        // Get the return value of the second asyncthread
        var tickers2 = r2.wait()
        
        // Print result
        Log("tickers1:", tickers1)
        Log("tickers2:", tickers2)
    }
    
    # Not supported
    
    // Not supported
    
  • The use of the function HttpQuery(...) in the backtest system: Data can be obtained by using HttpQuery(...) to send requests (only support GET requests) in the backtest system. A limit of 20 times is imposed in the backtest and HttpQuery(...) access will cache the data, while the function HttpQuery(...) returns the cached data on the second access to the same URL (no more actual web requests).

    We can run a service program on a server or a device that responds to the requests sent by HttpQuery(...) in the strategy program, and the service program in Go language for testing is shown as follows:

    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)
        }()
    }
    
    func main () {
        fmt.Println("listen http://localhost:9090")
        http.HandleFunc("/data", Handle)
        http.ListenAndServe(":9090", nil)
    }
    

    Use the function HttpQuery(...) to send requests during strategy backtesting:

    function main() {
        // You can write the IP address of the device where the service program is run
        Log(HttpQuery("http://xxx.xx.x.xxx:9090/data?msg=hello"));
        Log(exchange.GetAccount());
    }
    
    # If HttpQuery does not support Python, you can use Python urllib2 library
    
    void main() {
        //  You can write the IP address of the device where the service program is run
        Log(HttpQuery("http://xxx.xx.x.xxx:9090/data?msg=hello"));
        Log(exchange.GetAccount());
    }
    

    img

    It supports the transcoding of the response data in the request, and it also supports common encoding. Specifying the PostData parameter: {method: "GET", charset: "GB18030"} can realize the response data transcoding (GB18030).

Encode(…)

Encode(algo, inputFormat, outputFormat, data, keyFormat, key string), the function encodes the data according to the passed parameters, and returns a string value.

The parameter algo is the algorithm used for encoding calculation, which can be set to: “raw” (no algorithm), “sign”, “signTx”, “md4”, “md5”, “sha256”, “sha512”, “sha1”, “keccak256”, “sha3.224”, “sha3.256”, “sha3.384”, “sha3.512”, “sha3.keccak256”, “sha3.keccak512”, “sha512.384”, “sha512.256”, “sha512.224”, “ripemd160”, “blake2b.256”, “blake2b.512”, “blake2s.128”, “blake2s.256”. The parameter data is the data to be processed. The inputFormat/outputFormat/keyFormat parameters support encoding methods such as raw, hex, base64 and string. If the keyFormat parameter is not empty, the key parameter is used for encryption (HMAC), otherwise the default key is used. When the ```alg