The resource loading... loading...

FMZ API Instructions

Author: Zero, Created: 2020-04-20 10:19:00, Updated: 2023-04-12 14:44:56

sted IP address is 10.0.3.15 }


#### exchange.SetTimeout(...)

```exchange.SetTimeout(Millisecond)```, in which the parameter **Millisecond** is a millisecond value.

Only for the ```rest``` protocol, it is used to set the time-out period for ```rest``` requests, and it takes effect only by setting it once.
For example: ```exchange.SetTimeout(3000)```, set the timeout time of the ```rest``` request of the exchange object ```exchange```, if it exceeds 3 seconds, timeout will return ```null```.

Note:
* The parameter ```Millisecond``` is millisecond, and 1,000 milliseconds equals 1 second. 
* Only need to set once.
* Only for the **rest** protocol.
* ```SetTimeout``` is not a global function, but an exchange object method. 

### Special Requirements for C++ Written Strategies   
The main difference between ```C++``` written strategy and ```JavaScript``` written strategy is the returned data differences of **FMZ API** interface. For example, the ```exchange.GetTicker()``` function.

- JavaScript
```exchange.GetTicker()``` returns an object if the call succeeds, or returns ```null``` if the call fails (due to the exchange server problems or network problems, etc.).

```javascript
function main() {
    var ticker = exchange.GetTicker()
    // Determine if the call to "exchange.GetTicker" function failed, and return "null" when it failed
    if (ticker){
        Log(ticker)
    }
}
  • C++ exchange.GetTicker() returns an object, when the call succeeds. If the call fails, the returned object is still an object, which is distinguished from the normal returned object by the attribute Valid.

    void main() {
        auto ticker = exchange.GetTicker();
        // Determine if the call to "exchange.GetTicker()" function failed and if the "Valid" attribute of the returned object is "false"
        if (ticker.Valid) {
            Log(ticker);
        }
    }
    

The difference between the main() function in the C++ written strategy and the main() function in the standard C11: The return value of the C++ program’s entry function main() in C11 is of int type. In the C++ written strategy on FMZ platform, the startup function of the strategy is also the function main(), but these two are not the same function, just with the same name. On FMZ platform, the return value of the main() function in the C++ strategy is of void type.

void main() {
    // Use "Test" function to test
    if (!Test("c++")) {
        // Show an exception to stop the program
        Panic("Please download the latest-versioned docker");
    }

    // Determine if the return of all objects is valid with "Valid" 
    LogProfitReset();
    LogReset();
    Log(_N(9.12345, 2));
    Log("use _C", _C(exchange.GetTicker), _C(exchange.GetAccount));
}

JavaScript special matters

For JavaScript language reasons (JavaScript language built-in string supports ascii and utf-16 encoding only, in order not to lose data), when it encountered string that cannot be encoded, it will return the ArrayBuffer type. All the API interfaces that can pass string parameters also support passing the ArrayBuffer type.

JavaScript multi-threading

It truly supports the multi-threading function of the JavaScript language strategy from the bottom of the system, including: concurrent execution of custom execution functions; support for communication between concurrent threads, support for communication between concurrent threads and the main thread; storage, sharing of variables in the thread environment and other functions. It only supports the use in the live trading environment so far, please refer to: https://www.fmz.com/bbs-topic/9974.

__Thread

The __Thread(function, arguments...) function creates a thread that runs concurrently. It does not support direct reference to variables other than the thread execution function (running as an isolated environment). References to external variables will fail to compile. References to other closure functions are also not supported. All APIs of the platform can be called inside the thread, but other user-defined functions cannot be called. The parameter function can be a function reference or an anonymous function. The parameter arguments is the parameter of the function function (the actual parameter passed in), and arguments... means that multiple parameters can be passed in. Return value: the thread Id.

function testFunc(n) {
    Log("Execute the function testFunc, parameter n:", n)
}

function main() {
    var testThread1 = __Thread(function () {
        Log("Executes an anonymous function with no parameters.")
    })

    var testThread2 = __Thread(testFunc, 10)   // parameter n : 10
    
    __threadJoin(testThread1)                  // You can use the __threadJoin function to wait for concurrent threads to complete
    __threadJoin(testThread2)                  // If you don't wait for the execution of testThread1 and testThread2 to complete, the main thread will automatically release the concurrent thread after the execution is completed first, and terminate the execution function of the concurrent thread
}

It supports the calling method of __Thread([function, arguments...], [function, arguments...], ...), that is, multiple thread execution functions are executed sequentially in the created threads.

function threadTestFuncA(a) {
    Log(a)
    threadTestFuncC(4)
    
    // The threadTestFuncC function can be called, but the threadTestFuncB function cannot be called

    // this.d
    Log(d)
}

function threadTestFuncB(b) {
    Log(b)
    threadTestFuncC(2)

    this.d = 5
}

function main() {
    // Execute the threadTestFuncB function first, and then execute the threadTestFuncA function
    // threadTestFuncC will not be executed automatically, but it can be called by other thread execution functions
    var threadId = __Thread([threadTestFuncA, 3], [threadTestFuncB, 1], ["function threadTestFuncC(c) {Log(c)}"])
    __threadJoin(threadId)
}

The concurrent execution function passed to the __Thread function will be executed in reverse order. The above example will use the Log function to print 1 ~ 5 in sequence. Shared variables among different thread execution functions are supported. For example, the this.d variable in the above example can be assigned in the threadTestFuncB function and used in the threadTestFuncA function. It supports passing in function strings, such as "function threadTestFuncC(c) {Log(c)}" in the above example, which allows threads to execute function calls to external functions and libraries “imported” by this method.

For importing external libraries, a specific usage example is as follows:

function ml(input) {
    const net = new brain.NeuralNetwork();
    net.train([
        { input: [0, 0], output: [0] },
        { input: [0, 1], output: [1] },
        { input: [1, 0], output: [1] },
        { input: [1, 1], output: [0] },
    ]);
    return net.run(input);
}

function main() {
    Log(__threadJoin(__Thread([ml, [1, 0]], [HttpQuery("https://unpkg.com/brain.js")])))
}

__threadPeekMessage

The __threadPeekMessage(threadId, timeout) function reads data from the thread communication channel, the parameter threadId is the Id returned by the __Thread() function, setting the parameter threadId means to receive the data sent by the thread represented by the threadId. When it is set to 0, it means to receive the data sent by the main thread, that is, the current main function (the parameter threadId is set to 0, which is only supported in concurrent thread execution functions). The parameter timeout is a timeout setting, which will block and wait according to the number of milliseconds set by this parameter. If timeout is set to -1, it means to block and wait until the data in the channel is received. When the sender thread of the channel finishes execution and there is no data, the __threadPeekMessage function will return a null value immediately. Return value: received data.

When writing programs, you need to pay attention to the problem of thread deadlock. The following example is the communication between the execution function testFunc of the created concurrent thread and the main function of the main thread, and the thread execution function testFunc will be executed first.

function testFunc() {
    for(var i = 0 ; i < 5 ; i++) {                // 0 ~ 5, after sending to the main thread 5 times, the execution of the thread function is completed, and the __threadPeekMessage function in the main function fetches all the data, it will not block again, and returns a null value immediately
        __threadPostMessage(0, i)                 // Send data to the main thread
        var msg = __threadPeekMessage(0, -1)      // Listen for data from the main thread
        Log("from main msg:", msg)
        Sleep(500)
    }
    Log("testFunc execution is complete")
}

function main() {
    var testThread = __Thread(testFunc)           // Create a thread with an Id of 1 
    
    for (var i = 0 ; i < 10 ; i++) {
        __threadPostMessage(1, i)                 // Send data to the thread whose Id is 1, that is, the thread that executes the testFunc function in this example
        var msg = __threadPeekMessage(1, -1)      // Listen to the data sent by the thread whose Id is 1, that is, the data sent by the thread that executes the testFunc function in the example
        Log("from testFunc msg:", msg)
        Sleep(500)
    }
}

__threadPostMessage

The __threadPostMessage(threadId, data) function writes data to the thread communication channel, the parameter threadId is the Id returned by the __Thread() function, set the parameter threadId means to send data to the thread represented by the threadId, and when it is set to 0, it means to send data to the main thread, that is, the current main function (the parameter threadId is set to 0, which is only supported in concurrent thread execution functions). The parameter data can pass values, strings, Boolean values, objects, arrays and other types of data. This function has no return value.

When the __threadPostMessage function is called in the execution function of a thread to send signals and data, a message event will also be generated. You can use the EventLoop() function to receive message notifications.

function testFunc() {
    for(var i = 0 ; i < 10 ; i++) {
        Log("post msg, i:", i)
        __threadPostMessage(0, {msg: "testFunc", i: i})
        Sleep(100)
    }
}

function main() {
    var testThread = __Thread(testFunc)
    
    for (var i = 0 ; i < 10 ; i++) {
        var e = EventLoop()
        Log("e:", e)
        // e: {"Seq":1,"Event":"thread","Index":1,"Nano":1677745512064773600,"Deleted":0,"Symbol":"","Ticker":{"Info":null,"High":0,"Low":0,"Sell":0,"Buy":0,"Last":0,"Volume":0,"OpenInterest":0,"Time":0}}
        if (e.Event == "thread") {
            var msg = __threadPeekMessage(testThread, -1)
            Log("msg:", msg, "#FF0000")
        }
        Sleep(500)
    }
    
    var retThreadJoin = __threadJoin(testThread)
    Log("retThreadJoin:", retThreadJoin)
}

__threadJoin

The __threadJoin(threadId, timeout) function is used to wait for the thread with the specified Id to exit and reclaim system resources. The parameter threadId is the Id returned by the __Thread() function, and the parameter timeout is the timeout setting for waiting for the end of the thread, in milliseconds. If the timeout is not set, it means waiting until the end of the thread execution. Return value: The type is an object, indicating the execution result. If it times out, return undefined.

Return value structure, for example:

{
    "id":1,                 // Thread Id
    "terminated":false,     // Whether the thread is terminated forcibly
    "elapsed":2504742813,   // The running time of the thread (nanoseconds)
    "ret": 123              // The return value of the thread function
}

__threadTerminate

The __threadTerminate function is used to terminate the thread forcibly and release the hardware resources used by the created thread (the __threadJoin can no longer be used to wait for the end). The parameter threadId is the Id returned by the __Thread() function. Return value: Boolean value, indicating the execution result.

function testFunc() {
    for(var i = 0 ; i < 10 ; i++) {
        Log("i:", i)
        Sleep(500)
    }
}

function main() {
    var testThread = __Thread(testFunc)
    
    var retThreadTerminate = null 
    for (var i = 0 ; i < 10 ; i++) {
        Log("main i:", i)
        if (i == 5) {
            retThreadTerminate = __threadTerminate(testThread)
        }
        Sleep(500)
    }
    
    Log("retThreadTerminate:", retThreadTerminate)
}

__threadGetData

__threadGetData(threadId, key), the function is used to access the variables shared between threads. The data is valid when the thread has not executed the __threadJoin function (waiting for a successful exit) and has not executed the __threadTerminate function (terminated the thread forcibly). The parameter threadId is the thread Id, and the parameter key is the key name of the stored key-value pair. Return value: Returns the key value corresponding to key in the key-value pair.

threadId is 0 to indicate the main thread (i.e. the thread where main function is located), you can use __threadId() function to get the Id of the current thread, set the parameter threadId to the current thread Id, and use it to read the variables stored in the current thread in the thread execution function. You can also read the variables in the thread environment of the specified Id.

function main() {
    var t1 = __Thread(function() {
        Sleep(2000)
        var id = __threadId()                                                   // Get the Id of the current thread
        Log("id:", id, ", in testThread1 print:", __threadGetData(id, "msg"))   // Retrieve the key value corresponding to the key name msg in the current thread, i.e. "testThread2"
        Log("id:", 2, ", in testThread1 print:", __threadGetData(2, "msg"))     // Read the key value corresponding to the key name msg in the thread with thread Id 2, i.e. 99
    })

    var t2 = __Thread(function(t) {
        __threadSetData(t, "msg", "testThread2")                                // Set a key-value pair to the thread with Id t1 (Id 1), with the key name msg and the key value "testThread2"
        __threadSetData(__threadId(), "msg", 99)                                // Set the key-value pair in the current thread (Id is 2) with the key name msg and the key value 99
        __threadSetData(0, "msg", 100)                                          // Set up a key-value pair in the main thread, with the key name msg and the key value 100
    }, t1)
    
    __threadJoin(t1)   // You can check the __threadJoin(threadId, timeout) function, which is used to wait for the end of thread execution
    Log("in main, get msg:", __threadGetData(0, "msg"))
}

__threadSetData

__threadSetData(threadId, key, value), which is used to store variables in the thread environment. The parameter threadId is the thread Id, the parameter key is the key name of the stored key-value pair, and the parameter value is the key value. The function has no return value.

threadId is 0 to indicate the main thread (i.e. the thread where main function is located), and you can use the __threadId() function to get the Id of the current thread. value not specified means to delete key. It supports mutual access to shared variables between threads. The data is valid when the thread has not executed the __threadJoin function (waiting for successful exit) and has not executed the __threadTerminate function (terminate the thread forcibly). The value of the parameter value must be a serializable variable.

function testFunc() {
    var id = __threadId()                  // Get the current thread Id
    __threadSetData(id, "testFunc", 100)   // Stored in the current thread environment
    __threadSetData(0, "testFunc", 99)     // Stored in the main threaded environment
    Log("testFunc execution is complete")
}

function main() {
    // threadId is 1, the created thread with threadId 1 will be executed first, as long as the thread resources are not recycled, the variables stored locally in the thread will be valid
    var testThread = __Thread(testFunc)
    
    Sleep(1000)

    // Output in main, get testFunc: 100
    Log("in main, get testFunc:", __threadGetData(testThread, "testFunc"))

    // Output in main, get testFunc: 99
    Log("in main, get testFunc:", __threadGetData(0, "testFunc"))

    // Delete the testFunc key-value pair in the thread environment with Id testThread
    __threadSetData(testThread, "testFunc")

    // After deleting and reading again, the __threadGetData function returns undefined
    Log("in main, get testFunc:", __threadGetData(testThread, "testFunc"))
}

__threadId

__threadId(), which is used to obtain the Id of the current thread, without parameters. Return value: threadId of the current thread.

function testFunc() {
    Log("in testFunc, __threadId():", __threadId())
}

function main() {
    __Thread(testFunc)

    // If the execution of the main thread is completed, the created child thread will stop executing, so here Sleep(1000), wait for 1 second
    Sleep(1000)
    Log("in main, __threadId():", __threadId())
}

Support wasm encoding

In the JavaScript language strategy, the hex code of the wasm file can be loaded, instantiated, and the code in it executed. Compared with executing JavaScript code, it has a certain speed advantage.

wasm.parseModule

wasm.parseModule(data), which parses a hex string model. The data parameter is the wasm encoding that has been converted into a hex string. Return value: Return a wasm model object, you can refer to Strategy Example.

For example, the following c++ function code can be compiled into wasm code, and then converted into a hex string, which can be used as the data parameter of the wasm.parseModule(data) function.

// Recursive Algorithm for Fibonacci Numbers
int fib(int f) {
    if (f < 2) return f;
    return fib(f - 1) + fib(f - 2);   
}

wasm.buildInstance

wasm.buildInstance(module, opt), which creates a wasm model instance. The module parameter is the wasm model, and the opt parameter is the configuration information, which is used to set the stack space allocated to the wasm instance program. Return value: Returns a wasm model instance.

opt parameter setting example:

{
    stack_size: 65*1024*1024,
}

callFunction

callFunction(funcName, param1, ...), which is a method of the wasm model instance, used to execute the function in the wasm model instance. The funcName parameter is the name of the function to be executed, and the param1 parameter is the parameter passed in when executing the function (specified by the parameter funcName).

Blockchain

The FMZ Quant Trading platform is officially accessible to support the interactive call of the web3 contract on the chain, which can access the defi exchange easily.

Ethereum

Configuration

On the FMZ Quant Trading platform, write strategy code to implement the method call of smart contract on the Ethereum chain through the exchange.IO function. First, configure the access node on the FMZ Quant Trading platform. The access nodes can be self-built nodes or use third-party services, such as infura.

On the page of “Exchange” FMZ Quant Trading platform, select the protocol: cryptocurrency, and then selects the exchange Web3. Configure Rpc Address (service address of access node) and Private Key (private key). It supports localized deployment of private keys, see Key Security].

Register ABI

Calling a contract that is a standard ERC20 method does not require registration and it can be called directly. Calling methods other than the standard contract requires registering the ABI content: exchange.IO("abi", tokenAddress, abiContent). To get the ABI content of a contract, you can use the following URL to get it, taking the result field only.

https://api.etherscan.io/api?module=contract&action=getabi&address=0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45

Method of calling Ethereum RPC

When the second parameter of the exchange.IO function is "eth", you can call the RPC methods available to the node server directly, for example:

  • Query the balance of ETH in the wallet

    function main() {
        // "owner" needs to be replaced with a specific wallet address
        // "latest" parameter labels for string position: 'latest', 'earliest' or 'pending', please refer to https://eth.wiki/json-rpc/API#the-default-block-parameter
        // The return value ethBalance is a hexadecimal string: 0x9b19ce56113070
        var ethBalance = exchange.IO("api", "eth", "eth_getBalance", "owner", "latest")
    
        // ETH has a precision unit of 1e18
        var ethDecimal = 18
    
        // Because of the JavaScript language precision, it is necessary to use the system underlying encapsulated function BigInt, BigDecimal to process.
        // Convert ethBalance to readable quantity, 0x9b19ce56113070 to 0.043656995388076145.
        Log(Number((BigDecimal(BigInt(ethBalance))/BigDecimal(Math.pow(10, ethDecimal))).toString()))
    }
    
  • ETH Transfer

    function mian() {
        // ETH has a precision unit of 1e18
        var ethDecimal = 18
    
        // Number of transfers, readable quantity e.g. 0.01 ETH
        var sendAmount = 0.01
    
        // Because of the JavaScript language precision, it is necessary to use the system underlying encapsulated function BigInt, BigDecimal to process, and converts readable quantities into data for processing on the chain.
        var toAmount = (BigDecimal(sendAmount)*BigDecimal(Math.pow(10, ethDecimal))).toFixed(0)
        
        // "toAddress" is the address of the recipient's ETH wallet at the time of the transfer, which needs to be filled in specifically, and toAmount is the number of transfers.
        exchange.IO("api", "eth", "send", "toAddress", toAmount)
    }
    
  • Query gasPrice

    function toAmount(s, decimals) {
        return Number((BigDecimal(BigInt(s))/BigDecimal(Math.pow(10, decimals))).toString())
    }  
    
    function main() {
        var gasPrice = exchange.IO("api", "eth", "eth_gasPrice")
        Log("gasPrice:", toAmount(gasPrice, 0))   // 5000000000 , in wei (5 gwei)
    }
    
  • Query eth_estimateGas

    function toAmount(s, decimals) {
        // The toAmount function can convert the hex-encoded value to a decimal value
        return Number((BigDecimal(BigInt(s))/BigDecimal(Math.pow(10, decimals))).toString())
    }  
    
    function main() {
        // Encoding the call to the approve method
        var data = exchange.IO("encode", "0x111111111117dC0aa78b770fA6A738034120C302", "approve", "0xe592427a0aece92de3edee1f18e0157c05861564", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
        Log("data:", data)
        var gasPrice = exchange.IO("api", "eth", "eth_gasPrice")
        Log("gasPrice:", toAmount(gasPrice, 0))
        var obj = {
            "from" : "0x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",   // walletAddress
            "to"  : "0x111111111117dC0aa78b770fA6A738034120C302",
            "gasPrice" : gasPrice,
            "value" : "0x0",
            "data" : "0x" + data,
        }
        
        var gasLimit = exchange.IO("api", "eth", "eth_estimateGas", obj)
        Log("gasLimit:", toAmount(gasLimit, 0))
        Log("gas fee", toAmount(gasLimit, 0) * toAmount(gasPrice, 0) / 1e18)
    }
    

Support for encoding

The function exchange.IO encapsulates the encode method, which can return the function call encoding to hex string format. You can refer to the platform’s publicly available “Uniswap V3 Trading Class Library” template for specific use. The call of the encoding unwrapWETH9 method is used here as an example:

function main() {
    // Main network address of ContractV3SwapRouterV2: 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
    // To call the unwrapWETH9 method, you need to register the ABI first, omit the registration here.
    // "owner" represents the wallet address, it needs to fill in the specific, 1 represents the number of unwrapping, unwrap a WETH into ETH
    var data = exchange.IO("encode", "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", "unwrapWETH9(uint256,address)", 1, "owner")
    Log(data)
}

When calling the exchange.IO("encode",...) function, if the second parameter (string type) starts with 0x, it means the method call on the encoded (encode) smart contract. If it does not start with 0x, it is used to code the specified type order. The function is the same as the abi.encode in the solidity. Refer to the following example.

function main() {
    var x = 10 
    var address = "0x02a5fBb259d20A3Ad2Fdf9CCADeF86F6C1c1Ccc9"
    var str = "Hello World"
    var array = [1, 2, 3]
    var ret = exchange.IO("encode", "uint256,address,string,uint256[]", x, address, str, array)   // uint i.e. uint256 , the type length needs to be specified on FMZ
    Log("ret:", ret)
    /*
    000000000000000000000000000000000000000000000000000000000000000a    // x
    00000000000000000000000002a5fbb259d20a3ad2fdf9ccadef86f6c1c1ccc9    // address
    0000000000000000000000000000000000000000000000000000000000000080    // offset of str
    00000000000000000000000000000000000000000000000000000000000000c0    // offset of array
    000000000000000000000000000000000000000000000000000000000000000b    // the length of str
    48656c6c6f20576f726c64000000000000000000000000000000000000000000    // str data
    0000000000000000000000000000000000000000000000000000000000000003    // the length of the array
    0000000000000000000000000000000000000000000000000000000000000001    // array the first data
    0000000000000000000000000000000000000000000000000000000000000002    // array the second data
    0000000000000000000000000000000000000000000000000000000000000003    // array the third data
    */
}

Support the sequential encoding of tuples or types containing tuples:

function main() {
    var types = "tuple(a uint256,b uint8,c address),bytes"
    var ret = exchange.IO("encode", types, {
        a: 30,
        b: 20,
        c: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
    }, "0011")
    Log("encode: ", ret)
}

This type order is composed of tuple and bytes, so two parameters need to be passed in when calling exchange.IO to encode:

    1. Variables corresponding to type tuple:
    {
        a: 30,
        b: 20,
        c: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
    }
    

    The parameters passed in must also be consistent with the structure and type of tuple, as defined in the types parameter: tuple(a uint256, b uint8, c address).

    1. Variables corresponding to type bytes:
    "0011"
    

Support for sequential encoding of arrays or types containing arrays:

function main() {
    var path = ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "0xdac17f958d2ee523a2206206994597c13d831ec7"]   // ETH address, USDT address
    var ret = exchange.IO("encode", "address[]", path)
    Log("encode: ", ret)
}

Support for encodePacked

For example, when calling the DEX method of Uniswap V3, you need to pass in parameters, such as the exchange path, so you need to use the encodePackaged operation:

function main() {
    var fee = exchange.IO("encodePacked", "uint24", 3000)
    var tokenInAddress = "0x111111111117dC0aa78b770fA6A738034120C302"
    var tokenOutAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"
    var path = tokenInAddress.slice(2).toLowerCase()
    path += fee + tokenOutAddress.slice(2).toLowerCase()
    Log("path:", path)
}

Support for decode

Data processing not only supports encoding (encode), but also decoding (decode). Use the exchange.IO("decode", types, rawData) function to perform the decode operation.

function main() {
    // register SwapRouter02 abi
    var walletAddress = "0x398a93ca23CBdd2642a07445bCD2b8435e0a373f"
    var routerAddress = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
    var abi = `[{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"}]`
    exchange.IO("abi", routerAddress, abi)   // abi only uses the contents of the local exactOutput method, the full abi can be searched on the Internet

    // encode path
    var fee = exchange.IO("encodePacked", "uint24", 3000)
    var tokenInAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
    var tokenOutAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
    var path = tokenInAddress.slice(2).toLowerCase()
    path += fee + tokenOutAddress.slice(2).toLowerCase()
    Log("path:", path)

    var dataTuple = {
        "path" : path, 
        "recipient" : walletAddress, 
        "amountOut" : 1000, 
        "amountInMaximum" : 1, 
    }
    // encode SwapRouter02 exactOutput 
    var rawData = exchange.IO("encode", routerAddress, "exactOutput", dataTuple)
    Log("method hash:", rawData.slice(0, 8))   // 09b81346
    Log("params hash:", rawData.slice(8))

    // decode exactOutput params
    var decodeRaw = exchange.IO("decode", "tuple(path bytes,recipient address,amountOut uint256,amountInMaximum uint256)", rawData.slice(8))
    Log("decodeRaw:", decodeRaw)
}

The example performs the encodePacked operation first during the path parameter processing, because the exactOutput method call that needs to be encoded later needs the path as the parameter. Then, the encode method of the exactOutput routing contract has only one parameter, and the parameter type is tuple. The method exactOutput name is encoded as 0x09b81346, which is decoded the result decodeRaw by exchange.IO ("decode",...) method, and it is consistent with the variable dataTuple.

Support for switching private keys

It supports switching private keys to operate multiple wallet addresses, for example:

function main() {
    exchange.IO("key", "Private Key")   // "Private Key" represents the private key string, which needs to be filled in specifically
}

Method of calling the smart contract

The first parameter of the exchange.IO function is: "api" indicating that this call is an extension call. The second parameter of the exchange.IO function is the address of the smart contract to be called.

If the method called has the payable attribute, you need to add a transfer ETH value after the method name (the fourth parameter of the exchange.IO function), which can be of numeric type or pass a value in string form, e.g. the multicall method of Uniswap V3. The following contents are examples of some smart contract method calls:

  • decimals

    The decimals method is a constant method of ERC20 that does not generate gas consumption, and it can query the precision data of a particular token. The decimals method has no parameters. Return value: the precision data of token.

    function main(){
        var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302"    // The contract address of the token, in the example the token is 1INCH
        Log(exchange.IO("api", tokenAddress, "decimals"))                  // Query, print 1INCH tokens with precision index of 18
    }
    
  • allowance

    The allowance method is a constant method of ERC20 that does not generate gas consumption, and it can query the authorized amount of a certain token for a certain contract address. The allowance method need to pass in 2 parameters, the first one is the wallet address, and the second one is the authorized address. Return value: the authorization amount of token.

    function main(){
        // The contract address of the token, in the example the token is 1INCH
        var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302"            
    
        // For example, the query yields 10000000000000000000000, divided by the precision unit of the token 1e18, the current exchange object bound to the wallet to the spender address authorized 1 1INCH.
        Log(exchange.IO("api", tokenAddress, "allowance", "owner", "spender"))   
    }
    

    owner: The wallet address is replaced by the string “owner” in the example. In actual use, you need to fill in the address. spender: The authorized contract address is replaced by the string “spender” in the example. In actual use, you need to fill in the address specifically, for example, the address can be Uniswap V3 router v1.

  • approve

    The approve method is a non-constant method of ERC20 that generates gas consumption, which is used to authorize a token operation amount to a certain contract address. The approve method need to pass in 2 parameters, the first one is the address to be authorized and the second one is the authorized amount. Return value: txid.

    function main(){
        // The contract address of the token, in the example the token is 1INCH
        var tokenAddress = "0x111111111117dC0aa78b770fA6A738034120C302"   
    
        // The hexadecimal string of the authorization amount: 0xde0b6b3a7640000 , the corresponding decimal string: 1e18 , 1e18 divided by the precision unit of the token, i.e. 1 token amount, so this refers to the authorization of one token.
        Log(exchange.IO("api", tokenAddress, "approve", "spender", "0xde0b6b3a7640000"))  
    }
    

    spender: The address of the authorized contract, the example is replaced by the string ‘‘spender’’, the actual use needs to fill in the specific address, for example, it can be Uniswap V3 router v1 address. 0xde0b6b3a7640000: The number of authorizations, represented here using a hexadecimal string, corresponds to a decimal value of 1e18, divided by the token precision unit in the example (i.e., 1e18), yielding 1 token authorized.

    The third parameter of the exchange.IO function is passed the method name approve, which can also be written in the form of methodId, for example: “0x571ac8b0”. It is also possible to write the full standard method name, such as ‘‘approve(address,uint256)’’.

  • multicall

    The multicall method is a non-constant method of Uniswap V3, which will generate gas consumption and be used to exchange tokens in multiple ways. The multicall method may have multiple methods of passing in parameters. You can query the ABI containing the method for details. You need to register the ABI before calling the method. Return value: txid.

    For specific examples of multicall method calls, please refer to the public “Uniswap V3 Trading Class Library” template of our platform.

    Pseudocode is used here to describe some details:

    exchange.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", value, deadline, data)
    

    ContractV3SwapRouterV2: router v2 address of Uniswap V3. value: The amount of ETH transferred, set it to 0 if the tokenIn token for the exchange operation is not ETH. deadline: It can be set to (new Date().getTime() / 1000) + 3600, which means it is valid for one hour. data: The data of the packing operation to be performed.

    It is also possible to specify the gasLimit/gasPrice/nonce setting for method calls, we use pseudocode to describe again:

    exchange.IO("api", ContractV3SwapRouterV2, "multicall(uint256,bytes[])", value, deadline, data, {gasPrice: 123456, gasLimit: 21000})
    

    You can set parameter {gasPrice: 11, gasLimit: 111, nonce: 111} according to your specific needs, the parameter is set to the last parameter of the exchange.IO function. You can omit the nonce and use the system defaults, or do not set gasLimit/gasPrice/nonce and use all the system default values.

    It should be noted that in the example, the attribute of stateMutability in multicall(uint256,bytes[]) method is payable, and the value parameter needs to be passed in. The attribute of stateMutability":"payable" can be viewed from the ABI. The exchange.IO function will determine the required parameters according to the stateMutability attribute in the ABI that has been registered. If the stateMutability attribute is nonpayable, the parameter value does not need to be passed in.

Common function calls

  1. Gets the address of the wallet configured by the exchange object.
function main() {
    Log(exchange.IO("address"))         // Print the wallet address of the private key configured on the exchange object.
}
  1. Switch blockchain RPC nodes.
function main() {
    var chainRpc = "https://bsc-dataseed.binance.org"
    e.IO("base", chainRpc)    // Switch to BSC chain
}

Indicator Functions

When calling the indicator functions, you need to add TA. or talib. as prefix

Examples of calling the indicator functions in talib library and TA library:

function main(){
    var records = exchange.GetRecords()
    var macd = TA.MACD(records)
    var atr = TA.ATR(records, 14)

    // Print the last row of indicator values
    Log(macd[0][records.length-1], macd[1][records.length-1], macd[2][records.length-1])
    Log(atr[atr.length-1])

    // Print all indicator data, and JavaScript written strategies have integrated a talib library on FMZ Quant Trading platform 
    Log(talib.MACD(records))
    Log(talib.MACD(records, 12, 26, 9))
    Log(talib.OBV(records))

    // The talib library can also be passed in an array of numbers, which can be passed in order. For example: OBV (Records [Close], Records [Volume]) requires the parameters of the two arrays, including "Close" and "Volume"
    Log(talib.OBV([1,2,3], [7.1, 6.2, 3, 3]))

    // You can also directly pass in the "records" array containing the "Close" and "Volume" attribute
    Log(talib.OBV(records))
    Log(TA.Highest(records, 30, 'High'))
    Log(TA.Highest([1,2,3,4], 0))
}
# Python needs to install the talib library separately 
import talib
def main():
    r = exchange.GetRecords()
    macd = TA.MACD(r)
    atr = TA.ATR(r, 14)
    Log(macd[0][-1], macd[1][-1], macd[2][-1])
    Log(atr[-1])

    # For Python, the system extends the attributes of the array returned by GetRecords, and adds "Open", "High", "Low", "Close" and "Volume" to facilitate the call of the functions in the talib library
    Log(talib.MACD(r.Close))
    Log(talib.MACD(r.Close, 12, 26, 9))
    Log(talib.OBV(r.Close, r.Volume))

    Log(TA.Highest(r, 30, "High"))
    Log(TA.Highest([1, 2, 3, 4], 0))
void main() {
    auto r = exchange.GetRecords();
    auto macd = TA.MACD(r);
    auto atr = TA.ATR(r, 14);
    Log(macd[0][macd[0].size() - 1], macd[1][macd[1].size() - 1], macd[2][macd[2].size() - 1]);
    Log(atr[atr.size() - 1]);

    Log(talib.MACD(r));
    Log(talib.MACD(r, 12, 26, 9));
    Log(talib.OBV(r));

    Log(TA.Highest(r.Close(), 30));
}

The data in the following parameters are all the data obtained by function exchange.GetRecords(Period). Pay attention to the length of records, when the length does not meet the parameter calculation requirements of indicator function, an invalid value will be returned.

TA - Commonly-used Indicator Library

The TA indicator library of FMZ Quant Trading platform has optimized the commonly-used indicator algorithms to support the call of strategies written in JavaScript, Python and cpp open source TA library code.

MACD - Moving Average of Convergence & Divergence

TA.MACD(data, fast period, slow period, signal period), with the default period parameters of (12, 26, 9) returns the two-dimensional arrays, which are [DIF, DEA, MACD] respectively.

function main(){
    // You can fill in different k-line periods, such as PERIOD_M1, PERIOD_M30 and PERIOD_H1...
    var records = exchange.GetRecords(PERIOD_M15)
    var macd = TA.MACD(records, 12, 26, 9)
    // You can see from the log that three arrays are returned, corresponding to DIF, DEA, MACD
    Log("DIF:", macd[0], "DEA:", macd[1], "MACD:", macd[2])
}
def main():
    r = exchange.GetRecords(PERIOD_M15)
    macd = TA.MACD(r, 12, 26, 9)
    Log("DIF:", macd[0], "DEA:", macd[1], "MACD:", macd[2])
void main() {
    auto r = exchange.GetRecords(PERIOD_M15);
    auto macd = TA.MACD(r, 12, 26, 9);
    Log("DIF:", macd[0], "DEA:", macd[1], "MACD:", macd[2]);
}

KDJ - Stochastic Indicator

TA.KDJ(data, period 1, period 2, period 3), with the default period parameters of (9, 3, 3) returns the two-dimensional arrays, which are (K, D, J) respectively.

function main(){
    var records = exchange.GetRecords(PERIOD_M15)
    var kdj = TA.KDJ(records, 9, 3, 3)
    Log("k:", kdj[0], "d:", kdj[1], "j:", kdj[2])
}
def main():
    r = exchange.GetRecords(PERIOD_M15)
    kdj = TA.KDJ(r, 9, 3, 3)
    Log("k:", kdj[0], "d:", kdj[1], "j:", kdj[2])
void main() {
    auto r = exchange.GetRecords();
    auto kdj = TA.KDJ(r, 9, 3, 3);
    Log("k:", kdj[0], "d:", kdj[1], "j:", kdj[2]);
}

RSI - Relative Strength Index

TA.RSI(data, period), with the default period parameter of 14, returns a one-dimensional array.

function main(){
    var records = exchange.GetRecords(PERIOD_M30)
    var rsi = TA.RSI(records, 14)
    Log(rsi)
}
def main():
    r = exchange.GetRecords(PERIOD_M30)
    rsi = TA.RSI(r, 14)
    Log(rsi)
void main() {
    auto r = exchange.GetRecords(PERIOD_M30);
    auto rsi = TA.RSI(r, 14);
    Log(rsi); 
}

ATR - Average True Volatility

TA.ATR(data, period); ATR(data, period), with the default period parameter of 14, returns a one-dimensional array.

function main(){
    var records = exchange.GetRecords(PERIOD_M30)
    var atr = TA.ATR(records, 14)
    Log(atr)
}
def main():
    r = exchange.GetRecords(PERIOD_M30)
    atr = TA.ATR(r, 14)
    Log(atr)
void main() {
    auto r = exchange.GetRecords(PERIOD_M30);
    auto atr = TA.ATR(r, 14);
    Log(atr);
}

OBV - On Balance Volume

TA.OBV(data) returns a one-dimensional array.

function main(){
    var records = exchange.GetRecords(PERIOD_M30)
    var obv = TA.OBV(records)
    Log(obv)
}
def main():
    r = exchange.GetRecords(PERIOD_M30)
    obv = TA.OBV(r)
    Log(obv)
void main() {
    auto r = exchange.GetRecords(PERIOD_M30);
    auto obv = TA.OBV(r);
    Log(obv);
}

MA - Moving Average

TA.MA(data, period); MA(data, period), with the default period parameter of 9, returns a one-dimensional array.

function main(){
    var records = exchange.GetRecords(PERIOD_M30)
    var ma = TA.MA(records, 14)
    Log(ma)
}
def main():
    r = exchange.GetRecords(PERIOD_M30)
    ma = TA.MA(r, 14)
    Log(ma)
void main() {
    auto r = exchange.GetRecords(PERIOD_M30);
    auto ma = TA.MA(r, 14);
    Log(ma);
}

EMA - Exponential Moving Average

TA.EMA(data, period) is an exponential average indicator, with the default period parameter of 9, returns a one-dimensional array.

function main(){
    var records = exchange.GetRecords()
    // Determine if the number of K-line bars meets the requirement of the indicator calculation period 
    if (records && records.length > 9) {
        var ema = TA.EMA(records, 9)          
        Log(ema)
    }
}
def main():
    r = exchange.GetRecords()
    if r and len(r) > 9:
        ema = TA.EMA(r, 9)
        Log(ema)
void main() {
    auto r = exchange.GetRecords();
    if(r.Valid && r.size() > 9) {
        auto ema = TA.EMA(r, 9);
        Log(ema);
    }
}

BOLL - Bollinger Bands

TA.BOLL(data, period, multiplier); BOLL(data, period, multiplier) is Bollinger Band indicator, with the default parameters of (20, 2), and returns a two-dimensional array, namely [Upline, Midline, Downline].

function main() {
    var records = exchange.GetRecords()
    if(records && records.length > 20) {
        var boll = TA.BOLL(records, 20, 2)
        var upLine = boll[0]
        var midLine = boll[1]
        var downLine = boll[2]
        Log(upLine)
        Log(midLine)
        Log(downLine)
    }
}
def main():
    r = exchange.GetRecords()
    if r and len(r) > 20:
        boll = TA.BOLL(r, 20, 2)
        upLine = boll[0]
        midLine = boll[1]
        downLine = boll[2]
        Log(upLine)
        Log(midLine)
        Log(downLine)
void main() {
    auto r = exchange.GetRecords();
    if(r.Valid && r.size() > 20) {
        auto boll = TA.BOLL(r, 20, 2);
        auto upLine = boll[0];
        auto midLine = boll[1];
        auto downLine = boll[2];
        Log(upLine);
        Log(midLine);
        Log(downLine);
    }
}

Alligator - Alligator Indicator

TA.Alligator(data, mandible period, tooth period, upper lip period); Alligator(data, mandible period, tooth period, upper lip period) is Alligator indicator, with the default parameters of (13,8,5), and returns a two-dimensional array, namely [Mandible, Teeth, Upper Lip].

CMF - Chaikin Money Flow

TA.CMF(data, period); CMF(data, period) is Chaikin Money Flow indicator, with the default period parameter of 20, returns a one-dimensional array.

Highest - Period Highest Price

TA.Highest(data, period, attribute), returns the maximum value in the most recent period (excluding the current Bar), such as TA.Highest(records, 30, 'High'). If the period is 0, it means all Bars. If the attribute is not specified, the data is regarded as an ordinary array, and returns a price (value type).

Lowest - Period Lowest Price

TA.Lowest(data, period, attribute), returns the minimum value in the most recent period (excluding the current Bar), such as TA.Highest(records, 30, 'Low'). If the period is 0, it means all bars. If the attribute is not specified, the data is regarded as an ordinary array, and a price (value type) is returned.

The use of TA.Highest(...) and TA.Lowest(...) in the C++ strategy should be noted that Highest and Lowest functions only have 2 parameters respectively, and the first parameter is not the return value of auto r = exchange.GetRecords()function, so you need to call the method of r to pass specific attribute data, for example: pass r.Close() close price data. The call method of Close, High, Low, Open, Volume is just like r.Close().

C++ examples:

void main() { 
    Records r;
    r.Valid = true;
    for (auto i = 0; i < 10; i++) {
        Record ele;
        ele.Time = i * 100000;
        ele.High = i * 10000;
        ele.Low = i * 1000;
        ele.Close = i * 100;
        ele.Open = i * 10;
        ele.Volume = i * 1;
        r.push_back(ele);
    }

    for(int j = 0; j < r.size(); j++){
        Log(r[j]);
    }

    // Note: if the first parameter passed in is not r, you need to call "r.Close()"
    auto highest = TA.Highest(r.Close(), 8);   
    Log(highest);                     
}

Attached Third-Party Library

JavaScript

C++

Examples

  • JavaScript library http://mathjs.org/

    function main() {
        Log(math.round(math.e, 3))                // 2.718
        Log(math.atan2(3, -3) / math.pi)          // 0.75
        Log(math.log(10000, 10))                  // 4
        Log(math.sqrt(-4))                        // {"mathjs":"Complex","re":0,"im":2}   
    }
    

    http://mikemcl.github.io/decimal.js/

    function main() {
        var x = -1.2
        var a = Decimal.abs(x)
        var b = new Decimal(x).abs()
        Log(a.equals(b))                           // true  
    
        var y = 2.2
        var sum = Decimal.add(x, y)
        Log(sum.equals(new Decimal(x).plus(y)))

More