Basic Instructions
Getting Started
What can FMZ Quant Trading Platform do?
FMZ Quant Trading platform is the most professional quantitative community in the field of quantitative trading. Here you can learn, write, share, buy and sell quantitative strategies; you can conduct online backtesting and use simulation bots to conduct simulated trading; you can also run, publicize, and watch live trading. We support almost all mainstream digital currency exchanges.
Complete Series of Tutorials
Graphic Tutorials:
- Get Started with FMZ Quant Trading platform
- Elementary Tutorial for FMZ Quant Trading platform Strategy Writing
- Advanced Tutorial for FMZ Quant Trading platform Strategy Writing
If there is any problem, you can post questions and discuss in the forum at any time, or submit a ticket, or contact an administrator in the Telegram group (Telegram), generally speaking, the question will be answered quickly.
Supporting ChatGPT for development assistance
FMZ Quantitative Trading Platform has adopted ChatGPT as a development assistance tool, which can be accessed by clicking "ChatGPT" in the shortcut bar in the "Dashboard" to jump to the ChatGPT Auxiliary Tool Page.
Which programming languages are available to implement my strategies?
FMZ Quant Trading platform supports to use JavaScript, TypeScript, Python, C++, Pine Mylanguage and Blockly Visualization to write and design strategies.
It supports TypeScript language, still set it to JavaScript strategy when we create strategies, then we write // @ts-check at the beginning of the strategy code or click the button TypeScript in the top right corner of the strategy editing area to switch to TypeScript. The platform will recognize the code as TypeScript automatically and provide you with the appropriate compilation and type checking support for:
- Type safety:
TypeScript's static type checking function can help you find potential errors when writing code and improve code quality. - Automatic code completion:
TypeScript's type system makes it faster to find the attributes and methods you need when writing code, improving development efficiency. - Clearer code structure: With
TypeScript, you can better organize and maintain your code, making it easy to read and understand. - Powerful object-oriented programming features:
TypeScriptprovides powerful object-oriented programming features, such as interfaces, classes, generics and so on, helping you write more robust and reusable strategy code.
You only need to master one of these languages. In addition to supporting the way of designing strategies by writing code, you can also create strategies using visual modules (Blockly). The visualization module splicing and building strategy, without coding, adopts a more intuitive way to design strategies, which is very suitable for cultivating interest in strategy design so as to quickly get started with programmatic and quantitative trading.
Blockly Visualization Tutorials:
- Visualization Module to Build Trading Strategy - First Acquaintance
- Visualization Module to Build Trading Strategy - Advanced Understanding
- Visualization Module to Build Trading Strategy - In-depth
- Visualization Module to Build Trading Strategy - Simple Explanation
Set the
Pythoninterpreter used by thePythonstrategy program
Strategies written in Python, when backtesting or live trading, if the docker system environment has bothPython2 and Python3 installed, you can set the Python version to be launched at runtime on the first line of the strategy, such as #!python3 and #!python2, so that the system will find the interpreter automatically. And you can also specify an absolute path, such as: #!/usr/bin/python3.
What is Docker?
Docker can be understood as the executor of your trading strategy, responsible for complex data requests, data reception, network links, log postback and so on. The docker runs on your server, even if the FMZ Quant Trading platform website has a network failure, it will not affect the operation of your docker. The docker can run on Linux, Windows, Mac OS, Android, Raspberry Pi ARM Linux and other systems. Docker Page, Linux docker installation and update steps. The bots and logs managed by the docker are stored in the directory /logs/storage. The file is a Sqlite database file with db3, which can be edited directly by the Sqlite management software. For a file with extension db3 in the real bot database, the file name is the bot ID.
Protocols Supported
- Blockchain Assets: More than 50 mainstream blockchain assets (cryptocurrency) exchanges are now supported in our platform.
- General Protocol Access: general protocol
Strategy Security
When trading strategies are developed on FMZ Quant Trading platform, the strategy contents are only visible to the FMZ account holders. And on the FMZ quantitative trading platform, you can achieve complete localization of strategy code. For example, a strategy logic can be encapsulated into a Python package, which is loaded in the strategy code, so that the strategy content localization can be realized.
The security of Python code:
Because Python is an open-source language that is extremely easy to decompile, if the strategy is not for personal use but for rent, you can run the strategy on your own deployed docker and rent it out in the form of sub-account or full docker management if you are worried about strategy leakage.
The encryption of Python strategy code:
By default, Python strategy code is not encrypted when used by the author and encrypted when rented to others.
By editing the following code at the beginning of the Python strategy, you can specify whether to encrypt the strategy code for personal use or rental. The Python versions that support the encryption of strategy codes are as follows: Python 2.7, Python 3.5 and Python 3.6.
- Strategy authors run it themselves and encrypt the strategy code for others to use through registration code:
Specify#!pythonas the version of Python interpreter, and then use,to keep apart; input the encryption commandencrypt. If you don't specify the version ofPython, add#!,encryptdirectly.
python
#!python,encrypt
Or
python
#!encrypt
- It will not encrypt the strategy codes when strategy writers run for their own use and share with others through the registration code:
python
#!python, not encrypted
Or
python
#!not encrypted
Use code os.getenv('__FMZ_ENV__') to determine whether the encryption code is valid; the return of the string "encrypt" indicates that it has taken effect. It is only valid in the real bot, and the backtest will not encrypt the Python strategy codes.
python
#!encrypt
def main():
ret = os.getenv('__FMZ_ENV__')
# If the print variable ret is the string "encrypt" or ret == "encrypt" is true, that means the encryption is valid.
Log(ret, ret == "encrypt")
Key Security
The sensitive data, such as account information and encrypted strings in strategy parameters configured on the FMZ Quant Trading platform, are encrypted at the web browser. All information stored on FMZ Quant Trading platform is encrypted (not plaintext data), and only users' private devices can decrypt and use the information, which greatly improves the security of sensitive data. Please do not disclose or sell the strategies if other sensitive information is included in the strategy code, parameter settings and strategy descriptions etc.
- Our platform supports localized configuration of sensitive information, such as exchange account information and secret key
On the page where the platform configures exchange information, all encrypted text box controls with masks support the way of configuring the file path to load the docker's local file. The following takes theRSA KEYauthentication method of the exchange as an example to explain in detail how to configure sensitive information locally on the device where the docker program is located.
- Create RSA public key and private key. For example, create public and private keys in the format of
PKCS#8, there are many tools available for creation, such asopenssl. - Create an
RSA KEYon the exchange, and upload the public key created in Step 1 during creation. - Save the private key created in Step 1 in the same directory of the docker in the format of
txtfile, or in other paths in the directory of the docker program. - When configuring the exchange on the FMZ platform, fill in the
RSA KEYcreated by the exchange in the edit box of the configurationAccess Key. - When configuring the exchange on the FMZ platform, fill in the path of the
txtfile placed in the same level directory of the docker in the Step 3 in the edit box of the configurationSecret Key. For example, if the file name placed is:rsaKey.txt, and the file and the docker are filled in the same level directory:file:///rsaKey.txt. If the file is in the directory next to the directory of the docker programrsa_key, fill in:file:///rsa_key//rsaKey.txt. If you placersaKey. txtelsewhere on your computer or server follow these instructions accordingly, it should be noted that this file can only be placed at either same-level directories or subdirectories with respect to docker.
This makes it safer to localize and save the private key, you can refer to video explanation for detailed process.
Backtest System
What is a backtest system, and what is it used for?
After you have accomplished the design of a quantitative trading strategy, how can you know the basic situation of your strategy, such as the logic of the strategy and the direction of the strategy's returns? Of course, we cannot use real money directly to run the strategy on the real trading market, but we can use historical data to test your strategy and know the profits of your strategy in the historical data.
Is the backtest system data accurate and how about the accuracy of the backtest results?
FMZ Quant Trading platform divides the backtest system into real market level and simulation level. The real market level is to backtest completely according to the complete historical data; while simulation level backtest generates tick data according to the real K-line data at regular intervals for backtest. They are both based on the real historical data, but the real market level data is more accurate and the results are more credible. FMZ Backtest Mechanism Description. However, backtesting is just the performance of the strategy according to historical data. The historical data cannot fully represent the future market. The historical market may repeat, or it may also lead to the Black Swan. Therefore, the backtest results should be treated rationally and objectively.
Issues to be aware of when backtesting different programming language strategies:
The backtest of JavaScript and C++ trading strategies is conducted in the browser, and the real market bot or WexApp emulated exchange real market (i.e. the WexApp emulated exchange of FMZ Quant Trading platform) runs without installing any other software, libraries or modules.
The backtest of Python is performed on the docker; it can be performed on the public server added by FMZ Quant Trading platform, and it can also be performed on user's own docker. The real market operation and backtest both rely on the Python installed on the system where the docker is located. If some libraries are needed, they need to be installed manually (only common libraries are supported on public servers).
Backtest Data in the System
There are two types of FMZ Quant Trading platform backtest: simulation level backtest and real market level backtest. The simulation level backtest generates the simulated tick based on the underlying K-line period. Each K-line period will generate 12 backtesting time points; however, the real market level collects ticks actually, which will occur about once every few seconds, resulting in a large amount of data and slower backtest speed; so it cannot be backtested for a very long period of time. The backtest mechanism of FMZ allows the trading strategy to trade multiple times on a single K-line, avoiding the situation where the trading can only be executed at the close price. It is more accurate while taking into account the speed of backtest. For more detailed explanations, please refer to the Link.
Strategy DEBUG method in the backtesting system
JavaScript strategy backtesting debugging in Chrome DevTools
Exchanges Supported in the Backtesting System
-
Encrypted Currency (Cryptocurrency)
Name Type Instruction Bitfinex spot exchange object supporting limited trading pairs, such as: BTC_USD,ETH_USDandLTC_USD, etc. (notice the quote currency of the trading pairs isUSDdollar)Binance spot exchange object supporting limited trading pairs, such as: BTC_USDT,ETH_USDT,ETH_BTCandLTC_BTC, etc.OKX spot exchange object supporting limited trading pairs, such as: BTC_USDT,ETH_USDT,ETH_BTCandLTC_BTC, etc.Huobi spot exchange object supporting limited trading pairs, such as: BTC_USDT,ETH_USDT,ETH_BTCandLTC_BTC, etc.OKX Futures futures exchange object supporting limited trading pairs, such as: BTC_USDandETH_USD, etc.; the quote currency of the trading pairs isUSD; after the setting of specific contract code (please refer to the functionexchange.SetContractType), the contract is crypto-margined contract; the supported contract codes include:this_week,next_week,quarterandswapHuobiDM futures exchange object HuobiDM is Huobi Futures (Huobi Contract), supporting limited trading pairs, such as: BTC_USDandETH_USD,etc.; the quote currency of the trading pairs isUSD; after the setting of specific contract code (please refer to the functionexchange.SetContractType), the contract is crypto-margined contract; the supported contract codes include:this_week,next_week,quarterandswap.BitMEX futures exchange object the trading pair is XBT_USD; after the setting of specific contract code (please refer to the functionexchange.SetContractType), the contract is a crypto-margined contract; the supported contract code is:XBTUSDBinance Futures futures exchange object supporting limited trading pairs, such as: BTC_USDTandETH_USDT, etc.; the quote currency of the trading pairs isUSD; after the setting of specific contract code (please refer to the functionexchange.SetContractType), the contract is aUSDT- margined contract; the supported contract code isswapDeribit Options futures exchange object trading pairs are: BTC_USDandETH_USD; after the setting of specific contract code (please refer to the functionexchange.SetContractType), the contract is a crypto-margined contract; specific options contract codes need to be setFor the futures exchange objects in the backtest system, changing trading pairs is temporarily not supported in the strategy codes.
Simulation Level
The simulation level backtest is based on the underlying K-line data of the backtest system, simulating tick data within the framework of the highest price, lowest price, opening price, and closing price values of a given underlying K-line Bar according to a certain algorithm. The data will return real-time tick data when the interface is requested. For details, please refer to: FMZ Quant Simulation Level Backtest Mechanism Description.
Real Market Level
The real market level backtest is the actual tick level data in the Bar time series. For strategies based on tick level data, using real market level to backtest is closer to reality. In real market level backtest, tick data is real recorded data, not simulated one. It supports depth data, record data playback of market tradings, custom depth and each individual trading data. The maximum size of the real-market-level data backtest is up to a maximum of 50MB, with no limit on the backtest time range within the upper limit of the dataset. If you need to enlarge the backtest time range as much as possible, you can reduce the value of the depth gear setting and do not use each individual trading data to increase the backtest time range. Call GetDepth, GetTrades functions to obtain playback market data. At a moment of market data on the timeline, calling GetTicker,GetTrades, GetDepth and GetRecords will not push the time multiple times when the time moves on the backtest timeline (which will not trigger a jump to the next market data moment). Repeated calls to one of the above functions will push the backtest time to move on the backtest timeline (jump to the next market data moment). When the real market level is used for backtest, an earlier time is not recommended to choose. There may be no real-market-level data in the premature time period.
The real market level backtest currently supports:
- Binance
- OKX (OKX Spot)
- HuobiDM (Huobi Futures)
Backtesting System Parameter Optimization
The parameters optimization function of the backtest system of FMZ Quant Trading platform is to set optimizations according to every parameter optimization option during backtest, and the options are shown as follows:
- Minimum value: to limit the start value of the parameters.
- Maximum value: to limit the maximum value of the parameters after incremental changes.
- Step size: the incremental variable amount of the parameters.
Generate parameters combinations, and traverse all those combinations to backtest (namely backtesting each parameters combination once). Only strategy parameters of number type can be optimized in the backtesting system.
For example, set parameters optimization options on the backtest page:
The backtest of parameters optimization mode:
Save Backtest Settings
In the strategy editing page, in the pagination of "Backtest" (namely the backtest system), you can set options like backtest configurations and strategy parameters to backtest the strategy. Backtest settings refer to backtest time range, exchange platform, slippoint and service fee etc.; while strategy parameters are used to set parameter options for strategies. When the strategy configurations are all set, you can backtest the strategy according to the settings. So how do you save these configured settings for use in the next backtest (the options set during page refresh will be reset)? You can click the "Save Settings" button on the strategy page, and all backtest settings (including backtest configurations and strategy parameter settings) will be recorded in code form in the source code of the strategy. When opening the strategy editing page again and switching to the backtesting system, the recorded backtesting configuration information in the strategy code will be automatically configured on the backtesting page.
Take JavaScript strategy as an example, and click "Save Backtest Settings to Source File":
There are slight differences on "Save Backtest Settings to Source File" among JavaScript, Python, cpp and Mylanguage:
javascript
/*backtest
start: 2021-06-26 00:00:00
end: 2021-09-23 00:00:00
period: 1d
basePeriod: 1h
platforms: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
python
'''backtest
start: 2021-06-26 00:00:00
end: 2021-09-23 00:00:00
period: 1d
basePeriod: 1h
platforms: [{"eid":"Binance","currency":"BTC_USDT"}]
'''
c++
/*backtest
start: 2021-06-26 00:00:00
end: 2021-09-23 00:00:00
period: 1d
basePeriod: 1h
platforms: [{"eid":"Binance","currency":"BTC_USDT"}]
*/
Mylanguage:
(*backtest
start: 2021-06-26 00:00:00
end: 2021-09-23 00:00:00
period: 1d
basePeriod: 1h
platforms: [{"eid":"Futures_OKCoin","currency":"BTC_USD"}]
*)
Custom Data Source
The system uses the GET method to request a custom URL (publicly accessible URL) to obtain an external data source for backtest. The additional request parameters are as follows:
| Parameter | Meaning | Explanation |
|---|---|---|
| Symbol | Symbol Name | such as BTC_USD_OKCoin_EN |
| Eid | Exchanges | such as OKCoin_EN |
| Round | Price Accuracy | such as 3, the price in the returned data must be multiplied by 1000 and rounded |
| Vround | Quantity Accuracy | such as 2, the amount in the returned data must be multiplied by 100 and rounded |
| Period | Bar Period (Milliseconds) | such as 60,000 indicating the bar requesting one minute |
| Depth | Depth Levels | 1-20 |
| Trades | Whether Need to Split Data | true/false |
| From | Start Time | unix timestamp |
| To | End Time | unix timestamp |
Note:
text
Round and V-Round are two parameters designed to avoid losing the precision of floating-point numbers during network transmission. The price data, trading volume and order amount, are all transmitted using integers.
An example of the stitched data:
text
http://customserver:80/data?symbol=BTC_USD_OKCoin_EN&eid=OKCoin_EN&round=3&vround=3&period=900000&from=1564315200&to=1567267200
The returned format must be one of the following two formats (which will be recognized by the system automatically):
Ordinary Bar-Level Backtest
javascript
{
"schema":["time","open","high","low","close","vol"],
"data":[[1564315200000,9531300,9531300,9497060,9497060,787],[1564316100000,9495160,9495160,9474260,9489460,338]]
}
Tick-level backtest data (including market depth information, an array with a depth format of [price,volume]; there can be multiple levels of depth; "asks" refers to price ascending order, and "bids" refers to price reverse order.)
javascript
{
"schema":["time","asks", "bids","trades","close","vol"],
"data":[[1564315200000,[[9531300,10]], [[9531300,10]],[[1564315200000,0,9531300,10]],9497060,787]]
}
Description
| Field | Description |
|---|---|
| Schema | It specifies the attributes of the columns in the data array, which is case sensitive and is only limited to "time", "open", "high", "low", "close", "vol", "asks" and "bids" |
| Data | An array that stores data by schema |
Data Format
| Field | Description |
|---|---|
| asks/bids | [[price,volume],...] |
| trades | [[time,direction(0:buy,1:sell),price,volume],...] |
Providing funding rate data:
For example, when backtesting Binance Futures, it is necessary to have additional data of funding rate, which needs to be provided by custom data source. For example, the data structure of the funding rate requested during the backtest of Binance Futures is shown as follows:
{
"detail": {},
"symbol": "futures_binance.eth_usdt.funding",
"schema": ["time", "open", "high", "low", "close", "vol"],
"data": [
[1582876800000, 25289, 25289, 25289, 25289, 0],
[1582905600000, 30522, 30522, 30522, 30522, 0],
[1582934400000, 40998, 40998, 40998, 40998, 0],
...
[1626652800000, 198, 198, 198, 198, 0],
[1626681600000, 691, 691, 691, 691, 0], // The adjacent periodic interval is 8 hours
[1626710400000, 310, 310, 310, 310, 0], // The funding rate of Binance updates every 8 hours, and why the data of the funding rate turns out to be 310?
[1626739200000, 310, 310, 310, 310, 0], // Like the bars data, to avoid losing the precision of floating-point numbers during network transmission, the data uses integer, so the data needs to be processed according to round parameter; the data, returned to the backtest system after processing, is 310
[1626768000000, -41610, -41610, -41610, -41610, 0], // The funding rate might be a negative value
[1626796800000, -5125, -5125, -5125, -5125, 0],
...
[1627977600000, 10000, 10000, 10000, 10000, 0]
]
}
An example of the data request from the backtest system:
http://customserver:80/data?symbol=futures_binance.eth_usdt.funding&eid=Futures_Binance&round=8&vround=5&depth=20&trades=1&custom=0&period=3600000&from=1360771200&to=1628006400
Example for Custom Data Source:
Specify the data source, URL: http://xxx.xx.x.xx:9090/data
Customize the data server, written in golang:
mylang
package main
import (
"fmt"
"net/http"
"encoding/json"
)
func Handle (w http.ResponseWriter, r *http.Request) {
// e.g. set on backtest DataSourse: http://xxx.xx.x.xx:9090/data
// r.URL: /data?depth=20&detail=true&eid=Binance&from=1566820800&period=900000&round=3&symbol=BTC_USDT_Binance&to=1569686400&trades=1&vround=5
// response
defer func() {
// response data
/* e.g. data
{
"schema":["time","open","high","low","close","vol"],
"data":[
[1564315200000,9531300,9531300,9497060,9497060,787],
[1564316100000,9495160,9495160,9474260,9489460,338]
]
}
*/
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)
}
Testing strategy, JavaScript example:
javascript
/*backtest
start: 2019-07-28 00:00:00
end: 2019-07-29 00:00:00
period: 1m
platforms: [{"eid":"OKX","currency":"BTC_USDT","feeder":"http://120.24.2.20:9090/data"}]
*/
function main() {
var ticker = exchange.GetTicker()
var records = exchange.GetRecords()
Log(ticker)
Log(records)
}

Charts drawn by the custom data in the backtest system:

Strategy Print Information:

Local Backtest Engine
FMZ Quant Trading platform has open-sourced for the JavaScript version and the Python version of the local backtest engine, supporting setting Underlying K Line Period during backtesting.
Backtest Page Shortcut Keys
-
Shortcut key for switching between strategy "Editing" page and the "Backtesting" page

Use the key
Ctrl +,to switch back to "Backtest" page and "Edit Strategy" page. After hold down the keyCtrl, press the key,. -
Shortcut key for saving strategy

Use the key
Ctrl + sto save strategies. -
Shortcut for starting strategy backtest

Use the key
Ctrl + bto enable "Start Backtest".
Code Description
Entry Functions
| Function Name | Description |
|---|---|
main() | it is an entry function. |
onexit() | it is a cleanup function when exiting normally, its maximum execution time is 5 minutes, which can be left undeclared; if the timeout occurs, an interrupt error will be reported. |
onerror() | it is an abnormal exit function, its maximum execution time is 5 minutes, which can be left undeclared. The strategies written in Python and cpp don't support this function. |
init() | it is an initialization function, its strategy program will be called automatically when it starts running, which can be left undeclared. |
- Description:
-
- The backtest system doesn't support the function
onerror().
- The backtest system doesn't support the function
-
- If the function
onerror()is triggered in the bot, the functiononexit()will not be triggered.
- If the function
onexit()
onexit(), processing clean-up work, with a maximum execution time of 5 minutes, which is realized by the user.
javascript
function main(){
Log("Start running, stop after 5 seconds, and execute onexit function!")
Sleep(1000 * 5)
}
// onexit function implementation
function onexit(){
var beginTime = new Date().getTime()
while(true){
var nowTime = new Date().getTime()
Log("The program stops counting down..The cleaning starts and has passed:", (nowTime - beginTime) / 1000, "Seconds!")
Sleep(1000)
}
}
python
import time
def main():
Log("Start running, stop after 5 seconds, and execute onexit function!")
Sleep(1000 * 5)
def onexit():
beginTime = time.time() * 1000
while True:
ts = time.time() * 1000
Log("The program stops counting down..The cleaning starts and has passed:", (ts - beginTime) / 1000, "Seconds!")
Sleep(1000)
c++
void main() {
Log("Start running, stop after 5 seconds, and execute onexit function!");
Sleep(1000 * 5);
}
void onexit() {
auto beginTime = Unix() * 1000;
while(true) {
auto ts = Unix() * 1000;
Log("The program stops counting down..The cleaning starts and has passed:", (ts - beginTime) / 1000, "Seconds!");
Sleep(1000);
}
}
init()
The user implements the initialization function init(), which will automatically execute the function init() at the beginning of the strategy to complete the initialization task.
javascript
function main(){
Log("The first line of the code executed in the program!", "#FF0000")
Log("Exit!")
}
// Initialization Function
function init(){
Log("Initialization!")
}
python
def main():
Log("The first line of the code is executed!", "#FF0000")
Log("Exit!")
def init():
Log("Initialization!")
c++
void main() {
Log("The first line of the code is executed!", "#FF0000");
Log("Exit!");
}
void init() {
Log("Initialization!");
}
onerror()
The execution of function onerror() will be triggered, when an exception occurs. This function does not support strategies written inPython and cpp.
javascript
function main() {
var arr = []
Log(arr[6].Close)
}
function onerror() {
Log("error")
}
python
# not supported by python
c++
// not supported by C++
Classic Strategy Framework
In the strategies written in JavaScript, Python and cpp, the Sleep() function needs to be called in the main loop of those strategies. It will be used to control the backtracking speed. In the bot, it is used to control the strategy polling intervals, and also control the requesting frequency of accessing the API interface of the exchange.
-
Basic framework examples of cryptocurrency strategies:
javascriptfunction onTick(){ //Write strategy logic here, and it will be called constantly, such as printing market information Log(exchange.GetTicker()) } function main(){ while(true){ onTick() //The function "Sleep" is mainly used to control the polling frequency of cryptocurrency strategies to prevent accessing the exchange API interafce too frequently Sleep(60000) } }pythondef onTick(): Log(exchange.GetTicker()) def main(): while True: onTick() Sleep(60000)c++void onTick() { Log(exchange.GetTicker()); } void main() { while(true) { onTick(); Sleep(60000); } }Take the simplest example, if I want to place a buy order with a price of 100 and a quantity of 1 on the exchange every second, I can write it like this:
javascriptfunction onTick(){ // It is just an example; for all the assets will be used to place orders fast during backtest or in the bot, do not implement the example in the bot exchange. Buy(100, 1) } function main(){ while(true){ onTick() // The pause period can be customized in millisecond (1 second = 1000 milliseconds) Sleep(1000) } }pythondef onTick(): exchange.Buy(100, 1) def main(): while True: onTick() Sleep(1000)c++void onTick() { exchange.Buy(100, 1); } void main() { while(true) { onTick(); Sleep(1000); } }
Template library
The template library is a reusable code module in FMZ Quant Trading platform, working as a category of trading strategy codes. When creating a trading strategy, if the category is set to Template library, a template is added in the "Strategy" page of the account that currently logged in FMZ Quant Trading platform. After creation, it is no longer possible to modify the category to a normal strategy.
"Template library" in JavaScript:
"Template library" in Python:
"Template library" in cpp:
-
Export function of "Template library"
The export function is an interface function of "Template library", and it can be called by the strategy that refers to "Template library". The example code for the declaration and implementation of the exported function in the Template library is as follows:javascript/* -- This method is called directly with $.Test() after the strategy refers to the template -- The "main" function will not be triggered in the strategy, and it is only used as the entry point for template debugging */ $.Test = function() { Log('Test') } function main() { $.Test() }pythondef Test(): Log("template call") # Export "Test" function; the main strategy can be called by ext.Test() ext.Test = Testc++// The strategy refers to the template and calls this method directly with ext::Test() void Test() { Log("template call"); } -
"Template library" parameters
"Template library" can also set its own interface parameters, which are used in the form of global variables in the code of "Template library"."Template library" parameter settings:
Template library codes:
javascript$.SetParam1 = function(p1) { param1 = p1 } $.GetParam1 = function() { Log("param1:", param1) return param1 }pythondef SetParam1(p1): global param1 param1 = p1 def GetParam1(): Log("param1:", param1) return param1 ext.SetParam1 = SetParam1 ext.GetParam1 = GetParam1c++void SetParam1(float p1) { param1 = p1; } float GetParam1() { Log("param1:", param1); return param1; }Refer to the strategy code in the Template library example mentioned above:
javascriptfunction main () { Log("call $.GetParam1:", $.GetParam1()) Log("call $.SetParam1:", "#FF0000") $.SetParam1(20) Log("call $.GetParam1:", $.GetParam1()) }pythondef main(): Log("call ext.GetParam1:", ext.GetParam1()) Log("call ext.SetParam1:", "#FF0000") ext.SetParam1(20) Log("call ext.GetParam1:", ext.GetParam1())c++void main() { Log("call ext::GetParam1:", ext::GetParam1()); Log("call ext::SetParam1:", "#FF0000"); ext::SetParam1(20); Log("call ext::GetParam1:", ext::GetParam1()); } -
Quote "Template library"
After checking the reference in the template column of the strategy editing page, save the strategy.
Built-In Structure
Global Variables
Exchange
Exchangecan be regarded as an exchange object. By default, it is regarded as the first exchange object added in the strategy parameters. All data interaction with the exchange is realized through the functions in this object.
-
Adding exchange objects in "Backtest"

-
Adding exchange objects on "Bot" page

The added exchange objects correspond to the exchange objects in the code:
javascript
function main() {
Log("The name of the first exchange object added on the bot page or backtest page:", exchange.GetName(), ", Label:", exchange.GetLabel())
}
python
def main():
Log("The name of the first exchange object added on the bot page or backtest page:", exchange.GetName(), ", Label:", exchange.GetLabel())
c++
void main() {
Log("The name of the first exchange object added on the bot page or backtest page:", exchange.GetName(), ", Label:", exchange.GetLabel());
}
Exchanges
It can be understood as an array that stores all exchange objects like exchange exchange objects, which may contain multiple exchange objects; exchanges[0] is exchange.

The added exchange objects correspond to exchanges[0], exchanges[1], exchanges[2] ... and so on in the strategy code.
javascript
function main() {
for(var i = 0; i < exchanges.length; i++) {
Log("Index of the exchange object added (the first one is 0 and so on):", i, "Name:", exchanges[i].GetName(), "Label:", exchanges[i].GetLabel())
}
}
python
def main():
for i in range(len(exchanges)):
Log("Index of the exchange object added (the first one is 0 and so on):", i, "Name:", exchanges[i].GetName(), "Label:", exchanges[i].GetLabel())
c++
void main() {
for(int i = 0; i < exchanges.size(); i++) {
Log("Index of the exchange object added (the first one is 0 and so on):", i, "Name:", exchanges[i].GetName(), "Label:", exchanges[i].GetLabel());
}
}
Order Status
The attribute Status in the Order structure.
| Constant Name | Definition | Value |
|---|---|---|
| ORDER_STATE_PENDING | unfinished | 0 |
| ORDER_STATE_CLOSED | finished | 1 |
| ORDER_STATE_CANCELED | canceled | 2 |
| ORDER_STATE_UNKNOWN | unknown state (other states) | 3 |
ORDER_STATE_UNKNOWN status can call exchange.GetRawJSON() to get the original order status information, query the exchange file, and view the specific description.
The constant names in the form can be used directly in the strategy code to compare with the attribute Status in the Order structure, to ensure the order status by judging if the they are equal. Printing those constant names will show the constant names and their corresponding values, and other constant names down below work in the same way, so there will be no more detailed descriptions about them.
Order Transaction Type
The attribute Type in the Order structure.
| Constant Name | Definition | Value |
|---|---|---|
| ORDER_TYPE_BUY | Buy Order | 0 |
| ORDER_TYPE_SELL | Sell Order | 1 |
Position Type
The attribute Type in the Position structure.
| Constant Name | Definition | Description | Applicable | Value |
|---|---|---|---|---|
| PD_LONG | Long Position | Cryptocurrency futures use exchange.SetDirection("closebuy") to set the close position direction, and close this type of positions | Cryptocurrency futures | 0 |
| PD_SHORT | Short Position | Cryptocurrency futures use exchange.SetDirection("closesell") to set the close position direction, and close this type of positions | Cryptocurrency futures | 1 |
Futures Opening & Closing Position Directions
The attribute Offset in the Order structure.
| Constant Name | Definition | Value |
|---|---|---|
| ORDER_OFFSET_OPEN | Open Position Orders | 0 |
| ORDER_OFFSET_CLOSE | Close Position Orders | 1 |
Strategy Parameters
In the trading strategy codes, the strategy parameters set on the strategy interface are reflected in the form of global variables. JavaScript language can directly access the parameter values set or modified on the strategy interface; while in the functions of Python strategies, the keyword global is needed to modify the global variables in the strategy.
Parameter Types:
| Variable | Description | Remarks | Type | Default Value | Description |
|---|---|---|---|---|---|
| Number | Numeric type | Remarks | Number (number) | 1 | C++ strategy is a floating-point type |
| String | string | Remarks | String (string) | Hello FMZ | The default value does not need to be quoted. The input is treated as a string |
| Combox | ComboBox | Remarks | ComboBox (selected) | 1|2|3 | The combox variable itself is a numerical value, which represents the index of the column selected by the Combobox control. The value of the first ComboBox is 1, and others is 0, and so on |
| Bool | Check options | Remarks | Boolean (true/false) | true | If checked, the variable bool is true; if not checked, the variable bool is false |
| SecretString | Encrypted string | Remarks | Encrypted String (string) | Password | With the same usage as a string, the encrypted string will be sent by encryption and will not be transmitted in plain text |
- Interface parameters are set in the strategy parameter section below the code editing section of the strategy editing page.
- Interface parameters exist as global variables in the strategy code, that is, interface parameters can be modified in the code.
- The variable names of the interface parameters in the strategy code (seen in the above form):
number,string,combox,bool,secretString. - Description option: the names of the interface parameters on the strategy interface.
- Remark option: the detailed description of the interface parameters; the description will be displayed when the mouse is hovering over the interface parameters.
- Type option: the type of the interface parameters.
- Default value option: the default values of the interface parameters.
Parameter Dependency Settings:
One parameter can be set to allow another parameter to be displayed and hidden based on the selection of the parameter. For example, we set the parameter numberA, which is a numeric type. We let numberA be displayed or hidden based on whether parameter isShowA (boolean type) is true or false. We need to set the variable numberA on the interface parameters as: numberA@isShowA.
In this way, if the parameter isShowA is not checked, the parameter numberA is hidden. As for the parameters of ComboBox control type, the dependent part of the parameters is to judge whether the parameter value equals the index value of some option in the ComboBox. In the same way, take parameter isShowA as an example. When set the variables in the parameters, write: numberA@combox==2. The parameter numberA will display or hide, based on whether the parameter combox will be checked as the third option (where index 0 corresponds to the first option, index 1 corresponds to the second, and index 2 corresponds to the third.)
Strategy interface parameters, interactive controls, and parameter grouping function on "Template":
Just add (?First group) at the beginning of the description of the parameter that starts grouping, as shown in the following figure:
When you are using the strategy, the parameters are displayed in groups:
Save parameter default value:
The strategy parameters are shown in the figure. During the backtest, if you want to save the default values of the strategy parameters, you can click the Save settings button after modifying the strategy parameters.
You can save the strategy parameter settings in form of code:
javascript
/*backtest
start: 2020-02-29 00:00:00
end: 2020-03-29 00:00:00
period: 1d
args: [["number",2],["string","Hello FMZ.COM"],["combox",2],["bool",false],["numberA@isShowA",666],["isShowA",true]]
*/
python
'''backtest
start: 2020-02-29 00:00:00
end: 2020-03-29 00:00:00
period: 1d
args: [["number",2],["string","Hello FMZ.COM"],["combox",2],["bool",false],["numberA@isShowA",666],["isShowA",true]]
'''
c++
/*backtest
start: 2020-02-29 00:00:00
end: 2020-03-29 00:00:00
period: 1d
args: [["number",2],["string","Hello FMZ.COM"],["combox",2],["bool",false],["numberA@isShowA",666],["isShowA",true]]
*/
Data Structure
Some functions will be accompanied by the original JSON data requested during the call. The raw JSON data is stored in the attribute Info of the returned object. Since the backtest is not to access a platform interface, the returned data during the backtest has no attribute Info. The following is a description of the main attributes of each data structure.
Trade
Obtain all trading history (not itself), returned by the function exchange.GetTrades().
javascript
{
Id : 9585306, // Trading record ID; if the exchange interface does not provide order ID, use the timestamp to fill in
Time : 1567736576000, // Time (Unix timestamp milliseconds)
Price : 1000, // Price
Amount : 1, // Volume
Type : 0 // Order Type; refer to the order type in the constants; 0 is ORDER_TYPE_BUY, meaning the value of ORDER_TYPE_BUY is 0
}
Ticker
Market quotes are returned by the function exchange.GetTicker().
javascript
{
Info : {...}, // After requesting the platform interface, this attribute is not available in the raw data that the exchange interface responds to, during the backtest
High : 1000, // Highest price; if the platform interface does not provide the 24-hour highest price, use sell price 1 to fill in
Low : 500, // Lowest price; if the platform interface does not provide the 24-hour lowest price, use buy price 1 to fill in
Sell : 900, // Sell price 1
Buy : 899, // Buy price 1
Last : 900, // Last executed price
Volume : 10000000, // Recent trading volume; in principle, the unit of spot trading volume is base currency, and the unit of futures trading volume is contract quantity. If the platform interface does not provide this kind of data, use the existing data of the platform interface to fill in; for instance, it might be a trading volume in the unit of quote currency
Time : 1567736576000 // Millisecond-level timestamp
}
Record
The standard OHLC structure is used to draw K-lines and process indicator calculation and analysis. The function exchange.GetRecords() returns the structure array. Each Record structure represents a k-line bar, namely one k-line BAR. The Time in the Record is the starting time of the K-line bar period.
javascript
{
Time : 1567736576000, // A timestamp, accurate to millisecond, in the same format as the result obtained by Javascript's newDate().GetTime()
Open : 1000, // Open price
High : 1500, // Highest price
Low : 900, // Lowest price
Close : 1200, // Close price
Volume : 1000000 // Trading volume; in principle, the unit of spot trading volume is base currency, and the unit of futures trading volume is contract quantity. If the platform interface does not provide this kind of data, use the existing data of the platform interface to fill in; for instance, it might be a trading volume in the unit of quote currency
}
Order
Order structure can be returned by functions, including exchange.GetOrder() and exchange.GetOrders(). The function exchange.GetOrders() returns the array or an empty array of the structure (if there is no current unfinished order, return [], namely, an empty array).
javascript
{
Info : {...}, // After requesting the platform interface, this attribute is not available in the raw data that the exchange interface responds to, during the backtest
Id : 123456, // Unique identification 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().
javascript
{
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.
javascript
{
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.
javascript
{
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.:
javascript
function main(){
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body")
}
python
def main():
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body")
c++
void main() {
Mail("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body");
}
-
The
Mail_Gofunction is an asynchronous version of functionMail:
Its usage is similar to the functionexchange.Go.javascriptfunction main() { var r1 = Mail_Go("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body") var r2 = Mail_Go("smtp.163.com", "[email protected]", "password", "[email protected]", "title", "body") var ret1 = r1.wait() var ret2 = r2.wait() Log("ret1:", ret1) Log("ret2:", ret2) }python# Not supportedc++// 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).
javascript
function main() {
SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
}
python
def main():
SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
c++
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:
javascript
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)
}
python
def main():
order = exchange.GetOrder("123")
Log(order)
SetErrorFilter("502:|GetOrder")
order = exchange.GetOrder("123")
Log(order)
c++
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.
javascript
function main(){
var id = GetPid()
Log(id)
}
python
def main():
id = GetPid()
Log(id)
c++
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.
javascript
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)
}
python
def main():
exchange.GetOrder("123")
error = GetLastError()
Log(error)
c++
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.
javascript
function main(){
while(true) {
var cmd = GetCommand()
if (cmd) {
Log(cmd)
}
Sleep(1000)
}
}
python
def main():
while True:
cmd = GetCommand()
if cmd:
Log(cmd)
Sleep(1000)
c++
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:
Design interactive codes in the strategy:
javascript
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)
}
}
python
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)
c++
#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
javascript
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)
}
}
python
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)
c++
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:[email protected]: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. |
javascript
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()
}
}
python
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()
c++
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-1means to return immediately regardless of whether there is any message, such asws.read(-1).
Passing in parameter-2means 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 asws.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:javascriptfunction 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() }pythonimport 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()c++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:
javascriptvar 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") }pythonimport 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")c++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:
javascriptvar 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()") }pythonimport 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()")c++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:
javascriptfunction 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) }pythonimport 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)c++
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)
}
python
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)
c++
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
HttpQueryuses proxy settings:javascriptfunction 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:[email protected]:8889/http://www.baidu.com/") }python# If HttpQuery does not support Python, you can use Python urllib2 libraryc++void main() { HttpQuery("socks5://127.0.0.1:8889/http://www.baidu.com/"); HttpQuery("socks5://username:[email protected]:8889/http://www.baidu.com/"); } -
The function
HttpQuery's asynchronous versionHttpQuery_Go:
The using method is similar to the functionexchange.Go, such as accessing the public interface of the exchange asynchronously to obtain aggregated market data.javascriptfunction 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) }python# Not supportedc++// Not supported -
The use of the function
HttpQuery(...)in the backtest system:
Data can be obtained by usingHttpQuery(...)to send requests (only supportGETrequests) in the backtest system. A limit of 20 times is imposed in the backtest andHttpQuery(...)access will cache the data, while the functionHttpQuery(...)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:golangpackage 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:javascriptfunction 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()); }python# If HttpQuery does not support Python, you can use Python urllib2 libraryc++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()); }It supports the transcoding of the response data in the request, and it also supports common encoding.
Specifying thePostDataparameter:{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 algo parameter is set to sign or signTx, the key parameter is required.
javascript
function main(){
Log(Encode("md5", "raw", "hex", "hello"))
Log(Encode("sha512", "raw", "base64", "hello"))
Log(Encode("keccak256", "raw", "hex", "unwrapWETH9(uint256,address)"))
Log(Encode("raw", "string", "hex", "example")) // 6578616d706c65
Log(Encode("raw", "hex", "string", "6578616d706c65")) // example
}
python
def main():
Log(Encode("md5", "raw", "hex", "hello", "", ""))
Log(Encode("sha512", "raw", "base64", "hello", "", ""))
Log(Encode("keccak256", "raw", "hex", "unwrapWETH9(uint256,address)", "", ""))
Log(Encode("raw", "string", "hex", "example", "", ""))
Log(Encode("raw", "hex", "string", "6578616d706c65", "", ""))
c++
void main(){
Log(Encode("md5", "raw", "hex", "hello"));
Log(Encode("sha512", "raw", "base64", "hello"));
Log(Encode("keccak256", "raw", "hex", "unwrapWETH9(uint256,address)"));
Log(Encode("raw", "string", "hex", "example")); // 6578616d706c65
Log(Encode("raw", "hex", "string", "6578616d706c65")); // example
}
The parameter algo also supports encoding and decoding of strings, such as text.encoder.utf8, text.decoder.utf8, text.encoder.gbk and text.decoder.gbk.
javascript
function main(){
var ret1 = Encode("text.encoder.utf8", "raw", "hex", "hello") // e4bda0e5a5bd
Log(ret1)
var ret2 = Encode("text.decoder.utf8", "hex", "string", ret1)
Log(ret2)
var ret3 = Encode("text.encoder.gbk", "raw", "hex", "hello") // c4e3bac3
Log(ret3)
var ret4 = Encode("text.decoder.gbk", "hex", "string", ret3)
Log(ret4)
}
python
def main():
ret1 = Encode("text.encoder.utf8", "raw", "hex", "hello", "", "") # e4bda0e5a5bd
Log(ret1)
ret2 = Encode("text.decoder.utf8", "hex", "string", ret1, "", "")
Log(ret2)
ret3 = Encode("text.encoder.gbk", "raw", "hex", "hello", "", "") # c4e3bac3
Log(ret3)
ret4 = Encode("text.decoder.gbk", "hex", "string", ret3, "", "")
Log(ret4)
c++
void main(){
auto ret1 = Encode("text.encoder.utf8", "raw", "hex", "hello"); // e4bda0e5a5bd
Log(ret1);
auto ret2 = Encode("text.decoder.utf8", "hex", "string", ret1);
Log(ret2);
auto ret3 = Encode("text.encoder.gbk", "raw", "hex", "hello"); // c4e3bac3
Log(ret3);
auto ret4 = Encode("text.decoder.gbk", "hex", "string", ret3);
Log(ret4);
}
UnixNano()
UnixNano() returns nanosecond-level timestamp; if you need to obtain the millisecond-level timestamp, you can use the following code:
javascript
function main() {
var time = UnixNano() / 1000000
Log(_N(time, 0))
}
python
def main():
time = UnixNano()
Log(time)
c++
void main() {
auto time = UnixNano();
Log(time);
}
Unix()
Unix() returns a timestamp in seconds.
javascript
function main() {
var t = Unix()
Log(t)
}
python
def main():
t = Unix()
Log(t)
c++
void main() {
auto t = Unix();
Log(t);
}
GetOS()
GetOS() returns information about the system where the docker is located.
javascript
function main() {
Log("GetOS:", GetOS())
}
python
def main():
Log("GetOS:", GetOS())
c++
void main() {
Log("GetOS:", GetOS());
}
The log output of the docker run by Mac OS of Apple computer:
GetOS:darwin/amd64
darwin is the name of Mac OS system.
MD5(String)
MD5(String); parameter value: string type.
javascript
function main() {
Log("MD5", MD5("hello world"))
}
python
def main():
Log("MD5", MD5("hello world"))
c++
void main() {
Log("MD5", MD5("hello world"));
}
Log output:
MD5 5eb63bbbe01eeed093cb22bb8f5acdc3
DBExec(...)
DBExec(), its parameter value could be string, number or boolean, null and other types; return value: the object with the execution results in SQLite language.
DBExec(), the interface function of database, through passing parameters, can run the database of the bot (SQLite database). It realizes the adding, deleting, querying, and modifying operations on the bot database, supporting SQLite grammar. The system in the bot database save tables including: kvdb, cfg, log, profit and chart; do not operate any tables mentioned above. Note: the function DBExec() only supports the real bot.
-
Supports memory database
For the parameters of functionDBExec, if the sql statement is started with:, the operations in the memory database will be faster without writing files. It is suitable for the database operations without needs for long-term storage, for example:javascriptfunction main() { var strSql = [ ":CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ].join("") var ret = DBExec(strSql) Log(ret) // Add a piece of data Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")) // Query the data Log(DBExec(":SELECT * FROM TEST_TABLE;")) }pythondef main(): arr = [ ":CREATE TABLE TEST_TABLE(", "TS INT PRIMARY KEY NOT NULL,", "HIGH REAL NOT NULL,", "OPEN REAL NOT NULL,", "LOW REAL NOT NULL,", "CLOSE REAL NOT NULL,", "VOLUME REAL NOT NULL)" ] strSql = "" for i in range(len(arr)): strSql += arr[i] ret = DBExec(strSql) Log(ret) # Add a piece of data Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")) # Query the data Log(DBExec(":SELECT * FROM TEST_TABLE;"))c++void main() { string strSql = ":CREATE TABLE TEST_TABLE(\ TS INT PRIMARY KEY NOT NULL,\ HIGH REAL NOT NULL,\ OPEN REAL NOT NULL,\ LOW REAL NOT NULL,\ CLOSE REAL NOT NULL,\ VOLUME REAL NOT NULL)"; auto ret = DBExec(strSql); Log(ret); // Add a piece of data Log(DBExec(":INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);")); // Query the data Log(DBExec(":SELECT * FROM TEST_TABLE;")); } -
Create a table
javascript
function main() {
var strSql = [
"CREATE TABLE TEST_TABLE(",
"TS INT PRIMARY KEY NOT NULL,",
"HIGH REAL NOT NULL,",
"OPEN REAL NOT NULL,",
"LOW REAL NOT NULL,",
"CLOSE REAL NOT NULL,",
"VOLUME REAL NOT NULL)"
].join("")
var ret = DBExec(strSql)
Log(ret)
}
python
def main():
arr = [
"CREATE TABLE TEST_TABLE(",
"TS INT PRIMARY KEY NOT NULL,",
"HIGH REAL NOT NULL,",
"OPEN REAL NOT NULL,",
"LOW REAL NOT NULL,",
"CLOSE REAL NOT NULL,",
"VOLUME REAL NOT NULL)"
]
strSql = ""
for i in range(len(arr)):
strSql += arr[i]
ret = DBExec(strSql)
Log(ret)
c++
void main() {
string strSql = "CREATE TABLE TEST_TABLE(\
TS INT PRIMARY KEY NOT NULL,\
HIGH REAL NOT NULL,\
OPEN REAL NOT NULL,\
LOW REAL NOT NULL,\
CLOSE REAL NOT NULL,\
VOLUME REAL NOT NULL)";
auto ret = DBExec(strSql);
Log(ret);
}
- The adding, deleting, querying and modifying operations recorded in the table
javascript
function main() {
var strSql = [
"CREATE TABLE TEST_TABLE(",
"TS INT PRIMARY KEY NOT NULL,",
"HIGH REAL NOT NULL,",
"OPEN REAL NOT NULL,",
"LOW REAL NOT NULL,",
"CLOSE REAL NOT NULL,",
"VOLUME REAL NOT NULL)"
].join("")
Log(DBExec(strSql))
// Add a piece of data
Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
// Query the data
Log(DBExec("SELECT * FROM TEST_TABLE;"))
// Modify the data
Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000))
// Delete the data
Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110))
}
python
def main():
arr = [
"CREATE TABLE TEST_TABLE(",
"TS INT PRIMARY KEY NOT NULL,",
"HIGH REAL NOT NULL,",
"OPEN REAL NOT NULL,",
"LOW REAL NOT NULL,",
"CLOSE REAL NOT NULL,",
"VOLUME REAL NOT NULL)"
]
strSql = ""
for i in range(len(arr)):
strSql += arr[i]
Log(DBExec(strSql))
# Add a piece of data
Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"))
# Query the data
Log(DBExec("SELECT * FROM TEST_TABLE;"))
# Modify the data
Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000))
# Delete the data
Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110))
c++
void main() {
string strSql = "CREATE TABLE TEST_TABLE(\
TS INT PRIMARY KEY NOT NULL,\
HIGH REAL NOT NULL,\
OPEN REAL NOT NULL,\
LOW REAL NOT NULL,\
CLOSE REAL NOT NULL,\
VOLUME REAL NOT NULL)";
Log(DBExec(strSql));
// Add a piece of data
Log(DBExec("INSERT INTO TEST_TABLE (TS, HIGH, OPEN, LOW, CLOSE, VOLUME) VALUES (1518970320000, 100, 99.1, 90, 100, 12345.6);"));
// Query the data
Log(DBExec("SELECT * FROM TEST_TABLE;"));
// Modify the data
Log(DBExec("UPDATE TEST_TABLE SET HIGH=? WHERE TS=?", 110, 1518970320000));
// Delete the data
Log(DBExec("DELETE FROM TEST_TABLE WHERE HIGH=?", 110));
}
UUID()
UUID(), returns a 32-bit unique UUID, this function is available for real bots only.
javascript
function main() {
var uuid1 = UUID()
var uuid2 = UUID()
Log(uuid1, uuid2)
}
python
def main():
uuid1 = UUID()
uuid2 = UUID()
Log(uuid1, uuid2)
c++
void main() {
auto uuid1 = UUID();
auto uuid2 = UUID();
Log(uuid1, uuid2);
}
EventLoop(timeout)
EventLoop(timeout), returns after there is any websocket readable or exchange.Go, HttpQuery_Go and other concurrent tasks are completed. If the parameter timeout is set to 0, wait for an event to occur before returning. If it is greater than 0, set the event waiting timeout. If it is less than 0, return the latest event immediately. If the returned object is not null, the Event contained in the returned content is the triggered event type. This function is available for real bot trading only.
The first call of EventLoop in the code will initialize the mechanism of listening to the event. If the first call of EventLoop is started after the event callback, the previous event will be missed. The queue structure encapsulated by the underlying system will cache up to 500 event callbacks. If the EventLoop is not called in time for removal during program execution, the later event callbacks beyond the 500 cache will be lost. The call of EventLoop function will not affect the cache queue of websocket at the underlying system, nor the cache of exchange.Go and other concurrent functions. For these caches, you still need to use their own methods to retrieve data. For the data that has been taken out before the EventLoop function returns, no return event will be generated in the EventLoop function.
The main purpose of the EventLoop function is to notify the strategy layer that the underlying system has received new network data. Drive the whole strategy with events. When the EventLoop function returns an event, you only need to traverse all data sources. For example, the objects created by websocket connection and objects created by exchange.Go try to get data. You can refer to an open source class library design: class library link.
javascript
function main() {
var routine_getTicker = exchange.Go("GetTicker")
var routine_getDepth = exchange.Go("GetDepth")
var routine_getTrades = exchange.Go("GetTrades")
// Sleep(2000), if the Sleep statement is used here, the subsequent EventLoop function will miss the previous events, because after waiting for 2 seconds, the concurrent function has received the data, and the EventLoop monitoring mechanism will start later, and these events will be missed
// Unless you start calling EventLoop(-1) on the first line of code, first initialize the listening mechanism of EventLoop, you will not miss these events
// Log("GetDepth:", routine_getDepth.wait()) If the wait function is called in advance to get the result of the concurrent call of the GetDepth function, the event that the GetDepth function receives the request result will not be returned in the EventLoop function
var ts1 = new Date().getTime()
var ret1 = EventLoop(0)
var ts2 = new Date().getTime()
var ret2 = EventLoop(0)
var ts3 = new Date().getTime()
var ret3 = EventLoop(0)
Log("The first concurrent task completed was:", _D(ts1), ret1)
Log("The second concurrent task completed was:", _D(ts2), ret2)
Log("The third concurrent task completed was:", _D(ts3), ret3)
Log("GetTicker:", routine_getTicker.wait())
Log("GetDepth:", routine_getDepth.wait())
Log("GetTrades:", routine_getTrades.wait())
}
python
import time
def main():
routine_getTicker = exchange.Go("GetTicker")
routine_getDepth = exchange.Go("GetDepth")
routine_getTrades = exchange.Go("GetTrades")
ts1 = time.time()
ret1 = EventLoop(0)
ts2 = time.time()
ret2 = EventLoop(0)
ts3 = time.time()
ret3 = EventLoop(0)
Log("The first concurrent task completed was:", _D(ts1), ret1)
Log("The second concurrent task completed was:", _D(ts2), ret2)
Log("The third concurrent task completed was:", _D(ts3), ret3)
Log("GetTicker:", routine_getTicker.wait())
Log("GetDepth:", routine_getDepth.wait())
Log("GetTrades:", routine_getTrades.wait())
c++
void main() {
auto routine_getTicker = exchange.Go("GetTicker");
auto routine_getDepth = exchange.Go("GetDepth");
auto routine_getTrades = exchange.Go("GetTrades");
auto ts1 = Unix() * 1000;
auto ret1 = EventLoop(0);
auto ts2 = Unix() * 1000;
auto ret2 = EventLoop(0);
auto ts3 = Unix() * 1000;
auto ret3 = EventLoop(0);
Log("The first concurrent task completed was:", _D(ts1), ret1);
Log("The second concurrent task completed was:", _D(ts2), ret2);
Log("The third concurrent task completed was:", _D(ts3), ret3);
Ticker ticker;
Depth depth;
Trades trades;
routine_getTicker.wait(ticker);
routine_getDepth.wait(depth);
routine_getTrades.wait(trades);
Log("GetTicker:", ticker);
Log("GetDepth:", depth);
Log("GetTrades:", trades);
}
Built-In Functions
_G(K, V)
_G(K, V), with a function of a global dictionary that can be saved, supports both backtest and bot. After the backtest, the saved data will be cleared.
The data structure is KV table, stored in a local file permanently. Each bot has a separate database. It will always exists after restarting or when the docker exits. K must be a string, which is not case sensitive. V can be any JSON serializable content. When the function _G() is called and no parameters are passed in the bot operation, the function _G() returns the ID of the current bot.
javascript
function main(){
// Set a global variable num with a value of 1
_G("num", 1)
// Change a global variable num with the value "ok"
_G("num", "ok")
// Delete global variable num
_G("num", null)
// Return the value of the global variable num
Log(_G("num"))
// Delete all global variables
_G(null)
// Return bot ID
var robotId = _G()
}
python
def main():
_G("num", 1)
_G("num", "ok")
_G("num", None)
Log(_G("num"))
_G(None)
robotId = _G()
c++
void main() {
_G("num", 1);
_G("num", "ok");
_G("num", NULL);
Log(_G("num"));
_G(NULL);
// does not support auto robotId = _G();
}
Note:
When use the _G function to save data, it should be used reasonably according to the memory and hard disk space of the hardware device, and should not be abused. Otherwise, it may cause an memory overflow problem.
_D(Timestamp, Fmt)
_D(Timestamp, Fmt), returns the corresponding time strings of the specified timestamp. Parameter value: Timestamp is a numeric type, in millisecond. Fmt is a string type; Fmt defaults to : yyyy-MM-dd hh:mm:ss; return value: string type.
It returns the specified timestamp(ms) string, and returns the current time without passing any parameters; for example: _D() or _D(1478570053241), of which the default format is yyyy-MM-dd hh:mm:ss.
javascript
function main(){
var time = _D()
Log(time)
}
python
def main():
strTime = _D()
Log(strTime)
c++
void main() {
auto strTime = _D();
Log(strTime);
}
Note:
When use _D() in the Python strategy, we need to pay attention that the parameters passed in are timestamps in second (the millisecond level timestamps in the JavaScript and C ++ strategies, and 1 second = 1000 milliseconds).
In the bot, when using the function _D() to parse a time string with a readable timestamp, you need to pay attention to the time zone in the operation system of the docker program. The function _D() parses a timestamp as a readable time string based on the time of the docker system.
For example, parsing a timestamp of 1574993606000 with code:
javascript
function main() {
Log(_D(1574993606000))
}
python
def main():
# Beijing time server runs: 2019-11-29 10:13:26, and the docker on another server in another region runs this code will get the results: 2019-11-29 02:13:26
Log(_D(1574993606))
c++
void main() {
Log(_D(1574993606000));
}
_N(Num, Precision)
_N(Num, Precision), a formatted floating point number. Parameter value: Num is of numeric type; Precision is of integer type. Return value: numeric type.
For example: _N(3.1415, 2) will delete the value after two decimal places of 3.1415 and return 3.14.
javascript
function main(){
var i = 3.1415
Log(i)
var ii = _N(i, 2)
Log(ii)
}
python
def main():
i = 3.1415
Log(i)
ii = _N(i, 2)
Log(ii)
c++
void main() {
auto i = 3.1415;
Log(i);
auto ii = _N(i, 2);
Log(ii);
}
If you need to change N digits on the left of the decimal point to 0, you can write:
javascript
function main(){
var i = 1300
Log(i)
var ii = _N(i, -3)
// Checking the log shows that it is 1000
Log(ii)
}
python
def main():
i = 1300
Log(i)
ii = _N(i, -3)
Log(ii)
c++
void main() {
auto i = 1300;
Log(i);
auto ii = _N(i, -3);
Log(ii);
}
_C(...)
_C(function, args…) is a retry function, used for the fault tolerance of the interfaces about obtaining market information and acquiring unfinished orders, etc.
The interface will call the specified function continuously until it returns successfully (parameter function returns null value when calling the referenced function or false will retry the call). For example, _ C(exchange. GetTicker), the default retry interval is 3 seconds, which can call the function _CDelay (...) to set the retry interval, such as _CDelay (1000) means change function _C retry interval to 1 second.
For the following functions:
exchange.GetTicker()exchange.GetDepth()exchange.GetTrades()exchange.GetRecords()exchange.GetAccount()exchange.GetOrders()exchange.GetOrder()exchange.GetPosition()
They can all be called to do fault tolerance by function _C(...). The function _C(function, args...) is not limited to the fault tolerance of the functions listed above. The parameter function is quoted not called, and pay attention that it is _C(exchange.GetTicker), not _C(exchange.GetTicker()).
javascript
function main(){
var ticker = _C(exchange.GetTicker)
// Adjust _C() function's retry interval to 2 seconds
_CDelay(2000)
var depth = _C(exchange.GetDepth)
Log(ticker)
Log(depth)
}
python
def main():
ticker = _C(exchange.GetTicker)
_CDelay(2000)
depth = _C(exchange.GetDepth)
Log(ticker)
Log(depth)
c++
void main() {
auto ticker = _C(exchange.GetTicker);
_CDelay(2000);
auto depth = _C(exchange.GetDepth);
Log(ticker);
Log(depth);
}
For functions with parameters, when using _C(...) to do fault tolerance:
javascript
function main(){
var records = _C(exchange.GetRecords, PERIOD_D1)
Log(records)
}
python
def main():
records = _C(exchange.GetRecords, PERIOD_D1)
Log(records)
c++
void main() {
auto records = _C(exchange.GetRecords, PERIOD_D1);
Log(records);
}
It can also be used for custom function fault tolerance handling:
javascript
var test = function(a, b){
var time = new Date().getTime() / 1000
if(time % b == 3){
Log("Meet the criteria! ", "#FF0000")
return true
}
Log("Retry!", "#FF0000")
return false
}
function main(){
var ret = _C(test, 1, 5)
Log(ret)
}
python
import time
def test(a, b):
ts = time.time()
if ts % b == 3:
Log("Meet the criteria!", "#FF0000")
return True
Log("Retry!", "#FF0000")
return False
def main():
ret = _C(test, 1, 5)
Log(ret)
c++
// C++ does not support this method for fault tolerance of custom functions.
_Cross(Arr1, Arr2)
_Cross(Arr1, Arr2) returns the number of crossing periods of the arrays arr1 and arr2. A positive number is the period of upswing, and a negative number is the period of downswing, and 0 means the same as the current price. Parameter value: array of number type.
You can simulate a set of data to test the function _Cross(Arr1, Arr2):
javascript
// Fast line indicator
var arr1 = [1,2,3,4,5,6,8,8,9]
// Slow line indicator
var arr2 = [2,3,4,5,6,7,7,7,7]
function main(){
Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2))
Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1))
}
python
arr1 = [1,2,3,4,5,6,8,8,9]
arr2 = [2,3,4,5,6,7,7,7,7]
def main():
Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2))
Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1))
c++
void main() {
vector<double> arr1 = {1,2,3,4,5,6,8,8,9};
vector<double> arr2 = {2,3,4,5,6,7,7,7,7};
Log("_Cross(arr1, arr2) : ", _Cross(arr1, arr2));
Log("_Cross(arr2, arr1) : ", _Cross(arr2, arr1));
}
Visualize the simulated data for observation
Specific instructions: Built-In Function _Cross Analysis and Instructions
JSONParse(strJson)
JSONParse(strJson), the function is used to parse JSON strings. JSON strings containing large numbers can be parsed correctly, and large numbers will be parsed into string types. The backtesting system does not support this function.
javascript
function main() {
let s1 = '{"num": 8754613216564987646512354656874651651358}'
Log("JSON.parse:", JSON.parse(s1)) // JSON.parse: {"num":8.754613216564988e+39}
Log("JSONParse:", JSONParse(s1)) // JSONParse: {"num":"8754613216564987646512354656874651651358"}
let s2 = '{"num": 123}'
Log("JSON.parse:", JSON.parse(s2)) // JSON.parse: {"num":123}
Log("JSONParse:", JSONParse(s2)) // JSONParse: {"num":123}
}
python
import json
def main():
s1 = '{"num": 8754613216564987646512354656874651651358}'
Log("json.loads:", json.loads(s1)) # json.loads: map[num:8.754613216564987e+39]
Log("JSONParse:", JSONParse(s1)) # JSONParse: map[num:8754613216564987646512354656874651651358]
s2 = '{"num": 123}'
Log("json.loads:", json.loads(s2)) # json.loads: map[num:123]
Log("JSONParse:", JSONParse(s2)) # JSONParse: map[num:123]
c++
void main() {
auto s1 = "{\"num\":8754613216564987646512354656874651651358}";
Log("json::parse:", json::parse(s1));
// Log("JSONParse:", JSONParse(s1)); // The function is not supported
auto s2 = "{\"num\":123}";
Log("json::parse:", json::parse(s2));
// Log("JSONParse:", JSONParse(s2)); // The function is not supported
}
Custom Color
Each message string can end up with an RGB value such as #ff0000, which represents the foreground color to be displayed. If it is in a format such as #ff0000112233, the last six posteriors represent the background color.
javascript
function main() {
Log("Red", "#FF0000")
}
python
def main():
Log("Red", "#FF0000")
c++
void main() {
Log("Red", "#FF0000");
}
Log Information
When the bot is running, the log information is recorded in the database of the bot, which adopts the sqlite3 database. The database files locate in the device with the docker program, and the exact location of the files is in the dictionary of the docker program (robot executable program). For example: The bot database file with ID 130350 is in the directory ../logs/storage/130350 (.. is the dictionary where the docker of the robot is located), and the database file name is 130350.db3.
The logs in the backtest system can be downloaded by clicking the [Download Log] button at the bottom right corner of the backtest page after the backtest is over.
When you need to transfer the bot to a docker on another server, you can move the database files of the bot (database files with the extension "db3") to the transfer target server, and set the file name to the corresponding bot ID on the platform. In this way, all log information of the previous bot will not be lost due to the migration to the new device.
Log(...)
Log(message) means saving a message to the log list. Parameter value: message can be any type.
If you add the character @ after the string, the message will enter the push queue and be pushed to the current WeChat account of FMZ Quant Trading platform, and the "Email", "Telegram" and "WebHook" in the "Push settings" will be pushed (open the pages of Dashboard, Account and Push settings by orders to set bindings).
Note:
- Push is not supported in the "Debug Tool".
- Push is not supported in the "Backtest System".
javascript
function main() {
Log("Hello FMZ Quant!@")
Sleep(1000 * 5)
// Add the string to #ff0000, print the log in red, and push the message
Log("Hello, #ff0000@")
}
python
def main():
Log("Hello FMZ Quant!@")
Sleep(1000 * 5)
Log("Hello, #ff0000@")
c++
void main() {
Log("Hello FMZ Quant!@");
Sleep(1000 * 5);
Log("Hello, #ff0000@");
}
WebHook push:
Use the service program DEMO written in Golang:
golang
package main
import (
"fmt"
"net/http"
)
func Handle (w http.ResponseWriter, r *http.Request) {
defer func() {
fmt.Println("req:", *r)
}()
}
func main () {
fmt.Println("listen http://localhost:9090")
http.HandleFunc("/data", Handle)
http.ListenAndServe(":9090", nil)
}
Set WebHook: http://XXX.XX.XXX.XX:9090/data?data=Hello_FMZ
After run the service program, execute the strategy and push the information:
javascript
function main() {
Log("msg", "@")
}
python
def main():
Log("msg", "@")
c++
void main() {
Log("msg", "@");
}
Receive the push information, and the service program print the information:
listen http://localhost:9090
req: {GET /data?data=Hello_FMZ HTTP/1.1 1 1 map[User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/xx.x.xxxx.xxx Safari/537.36] Accept-Encoding:[gzip]] {} <nil> 0 [] false 1XX.XX.X.XX:9090 map[] map[] <nil> map[] XXX.XX.XXX.XX:4xxx2 /data?data=Hello_FMZ <nil> <nil> <nil> 0xc420056300}
Print the base64 encoded image
The function Log supports printing the images coded in base64, starts them with `, and ending with `, for example:
javascript
function main() {
Log("`data:image/png;base64,AAAA`")
}
python
def main():
Log("`data:image/png;base64,AAAA`")
c++
void main() {
Log("`data:image/png;base64,AAAA`");
}
Log supports printing the matplotlib.pyplot objects of Python directly, that is, as long as the objects contain savefig method, you can use Log to print directly, for example:
python
import matplotlib.pyplot as plt
def main():
plt.plot([3,6,2,4,7,1])
Log(plt)
Automatic language switching of printed logs
The function Log supports language switching; when the function outputs text, it will switch to the corresponding language automatically according to the language setting on the platform page. For example:
javascript
function main() {
Log("abc")
}
python
def main():
Log("abc")
c++
void main() {
Log("abc");
}
LogProfit(Profit)
LogProfit(Profit) records the profit value, prints the profit value, and draw a profit curve according to the profit value. Parameter value: Profit is numeric type.
If the function ends up with the character &, it can only realize drawing the profit chart, and not printing the profit log, such as: LogProfit(10, '&').
LogProfitReset()
LogProfitReset() clears all profit logs; you can take an integer value parameter to specify the number of reserved items.
javascript
function main() {
// Print 30 points on the income chart, then reset, and only retain the last 10 points
for(var i = 0; i < 30; i++) {
LogProfit(i)
Sleep(500)
}
LogProfitReset(10)
}
python
def main():
for i in range(30):
LogProfit(i)
Sleep(500)
LogProfitReset(10)
c++
void main() {
for(int i = 0; i < 30; i++) {
LogProfit(i);
Sleep(500);
}
LogProfitReset(10);
}
LogStatus(Msg)
LogStatus(Msg), the information is not saved in the log list, only the current status information of the bot is updated; it is displayed above the log and can be called multiple times to update the status. Parameter value: Msg can be any type.
javascript
function main() {
LogStatus('This is a normal status prompt')
LogStatus('This is a status prompt in red font # ff0000')
LogStatus('This is a multi-line status message \n I am the second line')
}
python
def main():
LogStatus('This is a normal status prompt')
LogStatus('This is a status prompt in red font # ff0000')
LogStatus('This is a multi-line status message \nI am the second line')
c++
void main() {
LogStatus("This is a normal status prompt");
LogStatus("This is a status prompt in red font # ff0000");
LogStatus("This is a multi-line status message \nI am the second line");
}
LogStatus(Msg) supports printing base64 encoded pictures, starting with `and ending up with`, such as: LogStatus("`data:image/png;base64,AAAA`").
LogStatus(Msg) supports direct import of Python's matplotlib.pyplot object, as long as the object contains the savefig method, you can pass in the function LogStatus(Msg), such as:
python
import matplotlib.pyplot as plt
def main():
plt.plot([3,6,2,4,7,1])
LogStatus(plt)
The data output example in the status bar:
javascript
function main() {
var table = {type: 'table', title: 'Position Information', cols: ['Column1', 'Column2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}
// After the JSON order is serialized, add the character "`" on both sides, which is regarded as a complex message format (currently supporting tables)
LogStatus('`' + JSON.stringify(table) + '`')
// Table information can also appear in multiple lines
LogStatus('First line message\n`' + JSON.stringify(table) + '`\nThird line message')
// That supports multiple tables displayed at the same time, and that will be displayed in a group with TAB
LogStatus('`' + JSON.stringify([table, table]) + '`')
// You can also construct a button in the table, and the strategy uses "GetCommand" to receive the content of the cmd attribute
var table = {
type: 'table',
title: 'Position operation',
cols: ['Column1', 'Column2', 'Action'],
rows: [
['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'close position'}]
]
}
LogStatus('`' + JSON.stringify(table) + '`')
// Or create a separate button
LogStatus('`' + JSON.stringify({'type':'button', 'cmd': 'coverAll', 'name': 'close position'}) + '`')
// You can customize the button style (button attribute of bootstrap)
LogStatus('`' + JSON.stringify({'type':'button', 'class': 'btn btn-xs btn-danger', 'cmd': 'coverAll', 'name': 'close position'}) + '`')
}
python
import json
def main():
table = {"type": "table", "title": "Position Information", "cols": ["Column1", "Column2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]}
LogStatus('`' + json.dumps(table) + '`')
LogStatus('First line message\n`' + json.dumps(table) + '`\nThird line message')
LogStatus('`' + json.dumps([table, table]) + '`')
table = {
"type" : "table",
"title" : "Position operation",
"cols" : ["Column1", "Column2", "Action"],
"rows" : [
["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}]
]
}
LogStatus('`' + json.dumps(table) + '`')
LogStatus('`' + json.dumps({"type": "button", "cmd": "coverAll", "name": "close position"}) + '`')
LogStatus('`' + json.dumps({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "close position"}) + '`')
c++
void main() {
json table = R"({"type": "table", "title": "Position Information", "cols": ["Column1", "Column2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]})"_json;
LogStatus("`" + table.dump() + "`");
LogStatus("First line message\n`" + table.dump() + "`\nThird line message");
json arr = R"([])"_json;
arr.push_back(table);
arr.push_back(table);
LogStatus("`" + arr.dump() + "`");
table = R"({
"type" : "table",
"title" : "Position operation",
"cols" : ["Column1", "Column2", "Action"],
"rows" : [
["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}]
]
})"_json;
LogStatus("`" + table.dump() + "`");
LogStatus("`" + R"({"type": "button", "cmd": "coverAll", "name": "close position"})"_json.dump() + "`");
LogStatus("`" + R"({"type": "button", "class": "btn btn-xs btn-danger", "cmd": "coverAll", "name": "close position"})"_json.dump() + "`");
}
Set the disable and description functions of the status bar buttons:
javascript
function main() {
var table = {
type: "table",
title: "Test the disable and description functions of status bar buttons",
cols: ["Column1", "Column2", "Column3"],
rows: []
}
var button1 = {"type": "button", "name": "button1", "cmd": "button1", "description": "This is the first button"}
var button2 = {"type": "button", "name": "button2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true}
var button3 = {"type": "button", "name": "button3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false}
table.rows.push([button1, button2, button3])
LogStatus("`" + JSON.stringify(table) + "`")
}
python
import json
def main():
table = {
"type": "table",
"title": "Test the disable and description functions of status bar buttons",
"cols": ["Column1", "Column2", "Column3"],
"rows": []
}
button1 = {"type": "button", "name": "button1", "cmd": "button1", "description": "This is the first button"}
button2 = {"type": "button", "name": "button2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": True}
button3 = {"type": "button", "name": "button3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": False}
table["rows"].append([button1, button2, button3])
LogStatus("`" + json.dumps(table) + "`")
c++
void main() {
json table = R"({
"type": "table",
"title": "Test the disable and description functions of status bar buttons",
"cols": ["Column1", "Column2", "Column3"],
"rows": []
})"_json;
json button1 = R"({"type": "button", "name": "button1", "cmd": "button1", "description": "This is the first button"})"_json;
json button2 = R"({"type": "button", "name": "button2", "cmd": "button2", "description": "This is the second button, set to disabled", "disabled": true})"_json;
json button3 = R"({"type": "button", "name": "button3", "cmd": "button3", "description": "This is the third button, set to enabled", "disabled": false})"_json;
json arr = R"([])"_json;
arr.push_back(button1);
arr.push_back(button2);
arr.push_back(button3);
table["rows"].push_back(arr);
LogStatus("`" + table.dump() + "`");
}
Set the style of the status bar buttons:
javascript
function main() {
var table = {
type: "table",
title: "status bar button style",
cols: ["default", "raw", "success", "information", "warning", "danger"],
rows: [
[
{"type":"button", "class": "btn btn-xs btn-default", "name": "default"},
{"type":"button", "class": "btn btn-xs btn-primary", "name": "raw"},
{"type":"button", "class": "btn btn-xs btn-success", "name": "success"},
{"type":"button", "class": "btn btn-xs btn-info", "name": "information"},
{"type":"button", "class": "btn btn-xs btn-warning", "name": "warning"},
{"type":"button", "class": "btn btn-xs btn-danger", "name": "danger"}
]
]
}
LogStatus("`" + JSON.stringify(table) + "`")
}
python
import json
def main():
table = {
"type": "table",
"title": "status bar button style",
"cols": ["default", "raw", "success", "information", "warning", "danger"],
"rows": [
[
{"type":"button", "class": "btn btn-xs btn-default", "name": "default"},
{"type":"button", "class": "btn btn-xs btn-primary", "name": "raw"},
{"type":"button", "class": "btn btn-xs btn-success", "name": "success"},
{"type":"button", "class": "btn btn-xs btn-info", "name": "information"},
{"type":"button", "class": "btn btn-xs btn-warning", "name": "warning"},
{"type":"button", "class": "btn btn-xs btn-danger", "name": "danger"}
]
]
}
LogStatus("`" + json.dumps(table) + "`")
c++
void main() {
json table = R"({
"type": "table",
"title": "status bar button style",
"cols": ["default", "raw", "success", "information", "warning", "danger"],
"rows": [
[
{"type":"button", "class": "btn btn-xs btn-default", "name": "default"},
{"type":"button", "class": "btn btn-xs btn-primary", "name": "raw"},
{"type":"button", "class": "btn btn-xs btn-success", "name": "success"},
{"type":"button", "class": "btn btn-xs btn-info", "name": "information"},
{"type":"button", "class": "btn btn-xs btn-warning", "name": "warning"},
{"type":"button", "class": "btn btn-xs btn-danger", "name": "danger"}
]
]
})"_json;
LogStatus("`" + table.dump() + "`");
}
Combine the functionGetCommand() to construct the interactive function of the status bar buttons:
javascript
function test1() {
Log("Call a custom function")
}
function main() {
while (true) {
var table = {
type: 'table',
title: 'operation',
cols: ['Column1', 'Column2', 'Action'],
rows: [
['a', '1', {
'type': 'button',
'cmd': "CoverAll",
'name': 'close position'
}],
['b', '1', {
'type': 'button',
'cmd': 10,
'name': 'Send value'
}],
['c', '1', {
'type': 'button',
'cmd': _D(),
'name': 'Call a function'
}],
['d', '1', {
'type': 'button',
'cmd': 'test1',
'name': 'Call a custom function'
}]
]
}
LogStatus(_D(), "\n", '`' + JSON.stringify(table) + '`')
var str_cmd = GetCommand()
if (str_cmd) {
Log("Received interactive data str_cmd:", "Types of:", typeof(str_cmd), "Value:", str_cmd)
if(str_cmd == "test1") {
test1()
}
}
Sleep(500)
}
}
python
import json
def test1():
Log("Call a custom function")
def main():
while True:
table = {
"type": "table",
"title": "Operation",
"cols": ["Column1", "Column2", "Action"],
"rows": [
["a", "1", {
"type": "button",
"cmd": "CoverAll",
"name": "close position"
}],
["b", "1", {
"type": "button",
"cmd": 10,
"name": "Send value"
}],
["c", "1", {
"type": "button",
"cmd": _D(),
"name": "Call a function"
}],
["d", "1", {
"type": "button",
"cmd": "test1",
"name": "Call a custom function"
}]
]
}
LogStatus(_D(), "\n", "`" + json.dumps(table) + "`")
str_cmd = GetCommand()
if str_cmd:
Log("Received interactive data str_cmd", "Types:", type(str_cmd), "Value:", str_cmd)
if str_cmd == "test1":
test1()
Sleep(500)
c++
void test1() {
Log("Call a custom function");
}
void main() {
while(true) {
json table = R"({
"type": "table",
"title": "Operation",
"cols": ["Column1", "Column2", "Action"],
"rows": [
["a", "1", {
"type": "button",
"cmd": "CoverAll",
"name": "close position"
}],
["b", "1", {
"type": "button",
"cmd": 10,
"name": "Send value"
}],
["c", "1", {
"type": "button",
"cmd": "",
"name": "Call a function"
}],
["d", "1", {
"type": "button",
"cmd": "test1",
"name": "Call a custom function"
}]
]
})"_json;
table["rows"][2][2]["cmd"] = _D();
LogStatus(_D(), "\n", "`" + table.dump() + "`");
auto str_cmd = GetCommand();
if(str_cmd != "") {
Log("Received interactive data str_cmd", "Type:", typeid(str_cmd).name(), "Value:", str_cmd);
if(str_cmd == "test1") {
test1();
}
}
Sleep(500);
}
}
When constructing a status bar button for interaction, input data is also supported, and the interactive command is ultimately captured by the GetCommand() function.
To add an input item to the data structure of a button control in the status bar, for example, add "input": {"name": "Number of opening orders", "type": "number", "defValue": 1} to {"type": "button", "cmd": "open", "name": "open position"}, you can make the button pop up a dialog box with an input box control when clicked (The default value in the input box is 1, which is set by the "defValue" data), and you can enter data to send with the button command. For example, when running the following test code, after clicking the "Open position" button, a dialog box with an input box pops up. After entering 111 in the input box and clicking "OK", the GetCommand function will capture the message: open:111.
javascript
function main() {
var tbl = {
type: "table",
title: "operation",
cols: ["column 1", "column2"],
rows: [
["Open position operation", {"type": "button", "cmd": "open", "name": "open position", "input": {"name": "number of opening positions", "type": "number", "defValue": 1}}],
["Close position operation", {"type": "button", "cmd": "coverAll", "name": "close all positions"}]
]
}
LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`")
while (true) {
var cmd = GetCommand()
if (cmd) {
Log("cmd:", cmd)
}
Sleep(1000)
}
}
python
import json
def main():
tbl = {
"type": "table",
"title": "operation",
"cols": ["column 1", "column 2"],
"rows": [
["Open position operation", {"type": "button", "cmd": "open", "name": "open position", "input": {"name": "number of opening positions", "type": "number", "defValue": 1}}],
["Close position operation", {"type": "button", "cmd": "coverAll", "name": "close all positions"}]
]
}
LogStatus(_D(), "\n", "`" + json.dumps(tbl) + "`")
while True:
cmd = GetCommand()
if cmd:
Log("cmd:", cmd)
Sleep(1000)
c++
void main() {
json tbl = R"({
"type": "table",
"title": "operation",
"cols": ["column 1", "column 2"],
"rows": [
["Open position operation", {"type": "button", "cmd": "open", "name": "open position", "input": {"name": "number of opening positions", "type": "number", "defValue": 1}}],
["Close position operation", {"type": "button", "cmd": "coverAll", "name": "close all positions"}]
]
})"_json;
LogStatus(_D(), "\n", "`" + tbl.dump() + "`");
while(true) {
auto cmd = GetCommand();
if(cmd != "") {
Log("cmd:", cmd);
}
Sleep(1000);
}
}
Combine the cells in the table drawn by the LogStatus(Msg) function:
-
Horizontal merger
javascriptfunction main() { var table = { type: 'table', title: 'position operation', cols: ['Column1', 'Column2', 'Action'], rows: [ ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'close position'}] ] } var ticker = exchange.GetTicker() // Add a row of data, merge the first and second cells, and output the ticker variable in the merged cell table.rows.push([{body : JSON.stringify(ticker), colspan : 2}, "abc"]) LogStatus('`' + JSON.stringify(table) + '`') }pythonimport json def main(): table = { "type" : "table", "title" : "position operation", "cols" : ["Column1", "Column2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}] ] } ticker = exchange.GetTicker() table["rows"].append([{"body": json.dumps(ticker), "colspan": 2}, "abc"]) LogStatus("`" + json.dumps(table) + "`")c++void main() { json table = R"({ "type" : "table", "title" : "position operation", "cols" : ["Column1", "Column2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}] ] })"_json; auto ticker = exchange.GetTicker(); json jsonTicker = R"({"Buy": 0, "Sell": 0, "High": 0, "Low": 0, "Volume": 0, "Last": 0, "Time": 0})"_json; jsonTicker["Buy"] = ticker.Buy; jsonTicker["Sell"] = ticker.Sell; jsonTicker["Last"] = ticker.Last; jsonTicker["Volume"] = ticker.Volume; jsonTicker["Time"] = ticker.Time; jsonTicker["High"] = ticker.High; jsonTicker["Low"] = ticker.Low; json arr = R"([{"body": {}, "colspan": 2}, "abc"])"_json; arr[0]["body"] = jsonTicker; table["rows"].push_back(arr); LogStatus("`" + table.dump() + "`"); }
-
Vertical merger
javascriptfunction main() { var table = { type: 'table', title: 'Table demo', cols: ['ColumnA', 'ColumnB', 'ColumnC'], rows: [ ['A1', 'B1', {'type':'button', 'cmd': 'coverAll', 'name': 'C1'}] ] } var ticker = exchange.GetTicker() var name = exchange.GetName() table.rows.push([{body : "A2 + B2:" + JSON.stringify(ticker), colspan : 2}, "C2"]) table.rows.push([{body : "A3 + A4 + A5:" + name, rowspan : 3}, "B3", "C3"]) // A3 is merged by the first cell in the previous row table.rows.push(["B4", "C4"]) // A2 is merged by the first cell in the previous row table.rows.push(["B5", "C5"]) table.rows.push(["A6", "B6", "C6"]) LogStatus('`' + JSON.stringify(table) + '`') }pythonimport json def main(): table = { "type" : "table", "title" : "Table demo", "cols" : ["ColumnA", "ColumnB", "ColumnC"], "rows" : [ ["A1", "B1", {"type": "button", "cmd": "coverAll", "name": "C1"}] ] } ticker = exchange.GetTicker() name = exchange.GetName() table["rows"].append([{"body": "A2 + B2:" + json.dumps(ticker), "colspan": 2}, "C2"]) table["rows"].append([{"body": "A3 + A4 + A5:" + name, "rowspan": 3}, "B3", "C3"]) table["rows"].append(["B4", "C4"]) table["rows"].append(["B5", "C5"]) table["rows"].append(["A6", "B6", "C6"]) LogStatus("`" + json.dumps(table) + "`")c++void main() { json table = R"({ "type" : "table", "title" : "Table demo", "cols" : ["ColumnA", "ColumnB", "ColumnC"], "rows" : [ ["A1", "B1", {"type": "button", "cmd": "coverAll", "name": "C1"}] ] })"_json; // In order to test, the code is short and easy to read, and the constructed data is used here json jsonTicker = R"({"High": 0, "Low": 0, "Buy": 0, "Sell": 0, "Last": 0, "Time": 0, "Volume": 0})"_json; auto name = exchange.GetName(); json arr1 = R"([{"body": "", "colspan": 2}, "C2"])"_json; arr1[0]["body"] = "A2 + B2:" + jsonTicker.dump(); json arr2 = R"([{"body": "", "rowspan": 3}, "B3", "C3"])"_json; arr2[0]["body"] = "A3 + A4 + A5:" + name; table["rows"].push_back(arr1); table["rows"].push_back(arr2); table["rows"].push_back(R"(["B4", "C4"])"_json); table["rows"].push_back(R"(["B5", "C5"])"_json); table["rows"].push_back(R"(["A6", "B6", "C6"])"_json); LogStatus("`" + table.dump() + "`"); }
Table pagination display of status bar:
javascript
function main() {
var table1 = {type: 'table', title: 'table1', cols: ['Column1', 'Column2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}
var table2 = {type: 'table', title: 'table2', cols: ['Column1', 'Column2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}
LogStatus('`' + JSON.stringify([table1, table2]) + '`')
}
python
import json
def main():
table1 = {"type": "table", "title": "table1", "cols": ["Column1", "Column2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]}
table2 = {"type": "table", "title": "table2", "cols": ["Column1", "Column2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]}
LogStatus("`" + json.dumps([table1, table2]) + "`")
c++
void main() {
json table1 = R"({"type": "table", "title": "table1", "cols": ["Column1", "Column2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]})"_json;
json table2 = R"({"type": "table", "title": "table2", "cols": ["Column1", "Column2"], "rows": [ ["abc", "def"], ["ABC", "support color #ff0000"]]})"_json;
json arr = R"([])"_json;
arr.push_back(table1);
arr.push_back(table2);
LogStatus("`" + arr.dump() + "`");
}
In addition to paginating tables, multiple tables can be arranged from top to bottom.
javascript
function main(){
var tab1 = {
type : "table",
title : "table1",
cols : ["1", "2"],
rows : []
}
var tab2 = {
type : "table",
title : "table2",
cols : ["1", "2", "3"],
rows : []
}
var tab3 = {
type : "table",
title : "table3",
cols : ["A", "B", "C"],
rows : []
}
tab1.rows.push(["jack", "lucy"])
tab2.rows.push(["A", "B", "C"])
tab3.rows.push(["A", "B", "C"])
LogStatus('`' + JSON.stringify(tab1) + '`\n' +
'`' + JSON.stringify(tab2) + '`\n' +
'`' + JSON.stringify(tab3) + '`')
Log("exit")
}
python
import json
def main():
tab1 = {
"type": "table",
"title": "table1",
"cols": ["1", "2"],
"rows": []
}
tab2 = {
"type": "table",
"title": "table2",
"cols": ["1", "2", "3"],
"rows": []
}
tab3 = {
"type": "table",
"title": "table3",
"cols": ["A", "B", "C"],
"rows": []
}
tab1["rows"].append(["jack", "lucy"])
tab2["rows"].append(["A", "B", "C"])
tab3["rows"].append(["A", "B", "C"])
LogStatus("`" + json.dumps(tab1) + "`\n" +
"`" + json.dumps(tab2) + "`\n" +
"`" + json.dumps(tab3) + "`")
c++
void main() {
json tab1 = R"({
"type": "table",
"title": "table1",
"cols": ["1", "2"],
"rows": []
})"_json;
json tab2 = R"({
"type": "table",
"title": "table2",
"cols": ["1", "2", "3"],
"rows": []
})"_json;
json tab3 = R"({
"type": "table",
"title": "table3",
"cols": ["A", "B", "C"],
"rows": []
})"_json;
tab1["rows"].push_back(R"(["jack", "lucy"])"_json);
tab2["rows"].push_back(R"(["A", "B", "C"])"_json);
tab3["rows"].push_back(R"(["A", "B", "C"])"_json);
LogStatus("`" + tab1.dump() + "`\n" +
"`" + tab2.dump() + "`\n" +
"`" + tab3.dump() + "`");
}
Operation result:

Note:
When the strategy bot is running on the bot page, if you scroll through the history records, the status bar will enter a dormant state and stop updating. The status bar data will only refresh when the log is on the first page.
The status bar supports outputting images coded in base64, and also supports outputting the images coded in base64 in the displayed tablets. Because the length of the string of the encoded image is normally very long, no sample code is provided.
EnableLog()
EnableLog(IsEnable) turns on or off the log recording for order information. Parameter value: isEnable is of bool type. If IsEnable is set to false, the order log will not be printed and it will not be written to the bot database.
Chart(...)
Chart(...), function of customizing charts drawing.
Chart({…}); the parameter is the HighCharts.StockChart parameters of HighStocks that can be serialized in JSON, adding a _isStock attribute to the native parameter. If you specify _isStock:false, it will be displayed as a normal chart.
Note:
If you set the attribute of _isStock to false, the chart used is: Highcharts, as shown in the chart:

If we set the attribute of _isStock to true, the chart used is: Highstocks (default_isStock as true), as shown in the chart:

To return to the object, you can call add(n, data) (n (such as 0) is the index of series, and data is the data written in the chart) to add data to the specified index series; call reset() to clear the chart data, and reset can take a numeric parameter and specify the number of reserved items.
You can call add(n, data, i) (i is the index of this data in the series) to change the data in the corresponding series.
It can be negative, "-1" refers to the last one, and "-2" is the penultimate one. For example, when drawing a line, modify the data at the last point of the line:
chart.add(0, [1574993606000, 13.5], -1), change the data of the last point of series[0].data.
For it supports to display multiple charts, you only need to pass in array parameters during configuration, such as: var chart = Chart([{…}, {…}, {…}]). For example, chart 1 has two series, chart 2 has one series, and chart 3 has one series. Then, specify series ID 0 and 1 when adding represents to update the data of two order Columns of chart 1; specifying the series ID 2 when adding refers to the first series of chart 2 Data; specifying the series ID3 refers to the data of the first series of chart 3.
HighStocks: http://api.highcharts.com/highstock
Attribute settings related to multi-chart display: example
For example, the chart configuration objects:
javascript
var cfgA = {
extension: {
// It does not participate in grouping, displayed separately, and its default is 'group'
layout: 'single',
// This is the specified height, which can be set to string "300px" (set "300", and "300px" will be displayed instead automatically)
height: 300,
// It is the unit value of the specified width, with a total value of 12
col: 8
},
title: {
text: 'Market Chart'
},
xAxis: {
type: 'datetime'
},
series: [{
name: 'Buy 1',
data: []
}, {
name: 'Sell 1',
data: []
}]
}
var cfgB = {
title: {
text: 'Spread chart'
},
xAxis: {
type: 'datetime'
},
series: [{
name: 'Spread',
type: 'column',
data: []
}]
}
var cfgC = {
_isStock: false,
title: {
text: 'Pie Chart'
},
series: [{
type: 'pie',
name: 'one',
// After specifying the initial data, you don't need to update it with the "add" function; Changing the chart configuration directly will update the series
data: [
["A", 25],
["B", 25],
["C", 25],
["D", 25]
]
}]
}
var cfgD = {
extension: {
layout: 'single',
// The unit value of the specified width, with a total value of 12
col: 8,
height: '300px'
},
title: {
text: 'Market Chart'
},
xAxis: {
type: 'datetime'
},
series: [{
name: 'Buy 1',
data: []
}, {
name: 'Sell 1',
data: []
}]
}
var cfgE = {
__isStock: false,
extension: {
layout: 'single',
col: 4,
height: '300px'
},
title: {
text: 'Pie Chart2'
},
series: [{
type: 'pie',
name: 'one',
data: [
["A", 25],
["B", 25],
["C", 25],
["D", 25]
]
}]
}
python
cfgA = {
"extension" : {
"layout" : "single",
"height" : 300,
"col" : 8
},
"title" : {
"text" : "Market Chart"
},
"xAxis" : {
"type" : "datetime"
},
"series" : [{
"name" : "Buy 1",
"data" : []
}, {
"name" : "Sell 1",
"data" : []
}]
}
cfgB = {
"title" : {
"text" : "Spread chart"
},
"xAxis" : {
"type" : "datetime"
},
"series" : [{
"name" : "Spread",
"type" : "column",
"data" : []
}]
}
cfgC = {
"__isStock" : False,
"title" : {
"text" : "Pie Chart"
},
"series" : [{
"type" : "pie",
"name" : "one",
"data" : [
["A", 25],
["B", 25],
["C", 25],
["D", 25]
]
}]
}
cfgD = {
"extension" : {
"layout" : "single",
"col" : 8,
"height" : "300px"
},
"title" : {
"text" : "Market Chart"
},
"series" : [{
"name" : "Buy 1",
"data" : []
}, {
"name" : "Sell 1",
"data" : []
}]
}
cfgE = {
"__isStock" : False,
"extension" : {
"layout" : "single",
"col" : 4,
"height" : "300px"
},
"title" : {
"text" : "Pie Chart2"
},
"series" : [{
"type" : "pie",
"name" : "one",
"data" : [
["A", 25],
["B", 25],
["C", 25],
["D", 25]
]
}]
}
c++
json cfgA = R"({
"extension" : {
"layout" : "single",
"height" : 300,
"col" : 8
},
"title" : {
"text" : "Market Chart"
},
"xAxis" : {
"type" : "datetime"
},
"series" : [{
"name" : "Buy 1",
"data" : []
}, {
"name" : "Sell 1",
"data" : []
}]
})"_json;
json cfgB = R"({
"title" : {
"text" : "Spread chart"
},
"xAxis" : {
"type" : "datetime"
},
"series" : [{
"name" : "Spread",
"type" : "column",
"data" : []
}]
})"_json;
json cfgC = R"({
"__isStock" : false,
"title" : {
"text" : "Pie Chart"
},
"series" : [{
"type" : "pie",
"name" : "one",
"data" : [
["A", 25],
["B", 25],
["C", 25],
["D", 25]
]
}]
})"_json;
json cfgD = R"({
"extension" : {
"layout" : "single",
"col" : 8,
"height" : "300px"
},
"title" : {
"text" : "Market Chart"
},
"series" : [{
"name" : "Buy 1",
"data" : []
}, {
"name" : "Sell 1",
"data" : []
}]
})"_json;
json cfgE = R"({
"__isStock" : false,
"extension" : {
"layout" : "single",
"col" : 4,
"height" : "300px"
},
"title" : {
"text" : "Pie Chart2"
},
"series" : [{
"type" : "pie",
"name" : "one",
"data" : [
["A", 25],
["B", 25],
["C", 25],
["D", 25]
]
}]
})"_json;
-
cfgA.extension.layoutattributeIf this attribute is set and the value is "single", the chart will not be superimposed (it will not be displayed as a tabbed label) and will be displayed separately (tiled display).
-
cfgA.extension.heightattributeThis attribute is used to set the height of the chart. The value can be a numeric type, or set to "300px" mode.
-
cfgA.extension.colattributeThis attribute is used to set the width of the chart. The page width is divided into 12 units in total, and setting "8" means that the chart occupies 8 units of width.
Run the complete example strategy:
The effect display of the chart configuration objects in the above examples:


-
For the data on the chart configuration objects, modify the chart configuration directly, and then update the chart to realize the data update:
For example, in the
JavaScriptcode part of the example (Complete Example):javascriptcfgC.series[0].data[0][1] = Math.random() * 100 cfgE.series[0].data[0][1] = Math.random() * 100 // "Update" is actually equivalent to resetting the configuration of the chart chart.update([cfgA, cfgB, cfgC, cfgD, cfgE])Update the data by
addmethod, such as adding an item to the pie chart, and here follows theJavaScriptcode part of the example (Complete Example):javascript// Add a data point to the pie chart; "add" can only update the data points added by the "add" method, the built-in data points cannot be updated later chart.add(3, { name: "ZZ", y: Math.random() * 100 }) -
Attached usage examples of
ChartfunctionSimple drawing example:
javascript// This chart is an object in JavaScript language. Before using the "Chart" function, we need to declare an object variable of a chart configuration var chart = { // It is marked as a general chart; if you are interested, you can change it to false and run it __isStock: true, // Zoom tool tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'}, // Title title : { text : 'Spread analysis chart'}, // Choose a range rangeSelector: { buttons: [{type: 'hour',count: 1, text: '1h'}, {type: 'hour',count: 3, text: '3h'}, {type: 'hour', count: 8, text: '8h'}, {type: 'all',text: 'All'}], selected: 0, inputEnabled: false }, // The horizontal axis of the coordinate axis is: x axis, and the currently set "Types of" is: time xAxis: { type: 'datetime'}, // The vertical axis of the coordinate axis is: y axis, and the default value is adjusted with the data size yAxis : { // Title title: {text: 'Spread'}, // Whether to enable the right vertical axis opposite: false }, // Data system column; this attribute holds each data system column (line, K-line diagram, label, etc.) series : [ // The index is 0, and the data in the data column is stored in the data array. {name : "line1", id : "line 1,buy1Price", data : []}, // The index is 1, and set dashStyle: 'shortdash', namely: set the dashed line {name : "line2", id : "line 2,lastPrice", dashStyle : 'shortdash', data : []} ] } function main(){ // Call the "Chart" function to initialize the chart var ObjChart = Chart(chart) // Empty ObjChart.reset() while(true){ // Get the timestamp of this polling, that is, a millisecond timestamp, used to determine the position of the X axis written to the chart var nowTime = new Date().getTime() // Get market data var ticker = _C(exchange.GetTicker) // Get "Buy 1" price from the return value of market data var buy1Price = ticker.Buy // To obtain the last executed price, in order to avoid the overlap of the 2 lines, we add 1 var lastPrice = ticker.Last + 1 // Use timestamp as X value and "Buy 1" price as Y value, and pass them into the data sequence of index 0 ObjChart.add(0, [nowTime, buy1Price]) // Same as above ObjChart.add(1, [nowTime, lastPrice]) Sleep(2000) } }pythonimport time chart = { "__isStock" : True, "tooltip" : {"xDateFormat" : "%Y-%m-%d %H:%M:%S, %A"}, "title" : {"text" : "Spread analysis chart"}, "rangeSelector" : { "buttons" : [{"type": "count", "count": 1, "text": "1h"}, {"type": "hour", "count": 3, "text": "3h"}, {"type": "hour", "count": 8, "text": "8h"}, {"type": "all", "text": "All"}], "selected": 0, "inputEnabled": False }, "xAxis": {"type": "datetime"}, "yAxis": { "title": {"text": "Spread"}, "opposite": False }, "series": [{ "name": "line1", "id": "line 1,buy1Price", "data": [] }, { "name": "line2", "id": "line 2,lastPrice", "dashStyle": "shortdash", "data": [] }] } def main(): ObjChart = Chart(chart) ObjChart.reset() while True: nowTime = time.time() * 1000 ticker = exchange.GetTicker() buy1Price = ticker["Buy"] lastPrice = ticker["Last"] + 1 ObjChart.add(0, [nowTime, buy1Price]) ObjChart.add(1, [nowTime, lastPrice]) Sleep(2000)c++void main() { // When write strategies in C++, try not to declare global variables that are not basic types, so the declaration of the chart configuration objects is in the "main" function json chart = R"({ "__isStock" : true, "tooltip" : {"xDateFormat" : "%Y-%m-%d %H:%M:%S, %A"}, "title" : {"text" : "Spread analysis chart"}, "rangeSelector" : { "buttons" : [{"type": "count", "count": 1, "text": "1h"}, {"type": "hour", "count": 3, "text": "3h"}, {"type": "hour", "count": 8, "text": "8h"}, {"type": "all", "text": "All"}], "selected": 0, "inputEnabled": false }, "xAxis": {"type": "datetime"}, "yAxis": { "title": {"text": "Spread"}, "opposite": false }, "series": [{ "name": "line1", "id": "line 1,buy1Price", "data": [] }, { "name": "line2", "id": "line 2,lastPrice", "dashStyle": "shortdash", "data": [] }] })"_json; auto ObjChart = Chart(chart); ObjChart.reset(); while(true) { auto nowTime = Unix() * 1000; auto ticker = exchange.GetTicker(); auto buy1Price = ticker.Buy; auto lastPrice = ticker.Last + 1.0; ObjChart.add(0, {nowTime, buy1Price}); ObjChart.add(1, {nowTime, lastPrice}); Sleep(2000); } }The example of drawing trigonometric curve:
javascript// The object used to initialize the chart var chart = { // Chart title title: {text: "line value triggers plotLines value"}, // The related settings of Y axis yAxis: { // The horizontal line perpendicular to y axis, used as a trigger line, is an array of structures where multiple trigger lines can be set plotLines: [{ // Trigger line value; set a number, and this line will be displayed in the corresponding numerical position value: 0, // Set the color of the trigger line color: 'red', // Width width: 2, // Labels displayed label: { // Label text text: 'Trigger value', // Center label position align: 'center' } }] }, // The related settings of X axis, and here the setting type is the time axis xAxis: {type: "datetime"}, series: [ {name: "sin", type: "spline", data: []}, // This is a more important data system column; you can set multiple data system column, according to the array index control {name: "cos", type: "spline", data: []} ] } function main(){ // Pi var pi = 3.1415926535897 // Variable for recording timestamp var time = 0 // Angle var angle = 0 // Coordinate y value, used to receive sine and cosine values var y = 0 // Call the API to initialize charts with "chart" objects var objChart = Chart(chart) // When initializing, clear the chart objChart.reset() // Set the value of the trigger line to 1 chart.yAxis.plotLines[0].value = 1 // Loop while(true){ // Get the timestamp of the current moment time = new Date().getTime() // The angle is increased by 5 degrees every 500ms, and the sine value is calculated y = Math.sin(angle * 2 * pi / 360) // Write the calculated y value to the data of the corresponding index of the chart; the first parameter of the "add" function is the specified index objChart.add(0, [time, y]) // Calculate the cosine value y = Math.cos(angle * 2 * pi / 360) objChart.add(1, [time, y]) // Increase by 5 degrees angle += 5 // Pause for 5 seconds, to avoid drawing too frequently and the data growing too fast Sleep(5000) } }pythonimport math import time chart = { "title": {"text": "line value triggers plotLines value"}, "yAxis": { "plotLines": [{ "value": 0, "color": "red", "width": 2, "label": { "text": "Trigger value", "align": "center" } }] }, "xAxis": {"type": "datetime"}, "series": [{"name": "sin", "type": "spline", "data": []}, {"name": "cos", "type": "spline", "data": []}] } def main(): pi = 3.1415926535897 ts = 0 angle = 0 y = 0 objChart = Chart(chart) objChart.reset() chart["yAxis"]["plotLines"][0]["value"] = 1 while True: ts = time.time() * 1000 y = math.sin(angle * 2 * pi / 360) objChart.add(0, [ts, y]) y = math.cos(angle * 2 * pi / 360) objChart.add(1, [ts, y]) angle += 5 Sleep(5000)c++void main() { json chart = R"({ "title": {"text": "line value triggers plotLines value"}, "yAxis": { "plotLines": [{ "value": 0, "color": "red", "width": 2, "label": { "text": "Trigger value", "align": "center" } }] }, "xAxis": {"type": "datetime"}, "series": [{"name": "sin", "type": "spline", "data": []}, {"name": "cos", "type": "spline", "data": []}] })"_json; auto pi = 3.1415926535897; auto ts = 0; auto angle = 0.0; auto y = 0.0; auto objChart = Chart(chart); objChart.reset(); chart["yAxis"]["plotLines"][0]["value"] = 1; while(true) { ts = Unix() * 1000; y = sin(angle * 2 * pi / 360); objChart.add(0, {ts, y}); y = cos(angle * 2 * pi / 360); objChart.add(1, {ts, y}); angle += 5; Sleep(5000); } }In the
cppstrategy, theChartfunction can also use an encoded string to configure the chart:c++void main () { Chart c = Chart(R"EOF({"chart":{"type":"line"},"title":{"text":"Simple chart"},"xAxis":{"title":{"text":"Date"}},"yAxis":{"title":{"text":"Number"}},"series":[{"name":"number","data":[]}]})EOF"); c.reset(); for (size_t i = 0; i < 10; i++) { // For example, int64 of "sprintf" function has different parameters in 32-bit and 64-bit, so it is best to use "toString" to transfer the platform-related types into strings and then pass c.add(0, format("[%s, %d]", toString(Unix() + i).c_str(), rand() % 100)); } }
-
A complex example of mixed charts
JavaScript Strategy Address
javascript
/*backtest
start: 2020-03-11 00:00:00
end: 2020-04-09 23:59:00
period: 1d
exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}]
*/
var chartCfg = {
subtitle: {
text: "subtitle",
},
yAxis: [{
height: "40%",
lineWidth: 2,
title: {
text: 'PnL',
},
tickPixelInterval: 20,
minorGridLineWidth: 1,
minorTickWidth: 0,
opposite: true,
labels: {
align: "right",
x: -3,
}
}, {
title: {
text: 'Profit',
},
top: "42%",
height: "18%",
offset: 0,
lineWidth: 2
}, {
title: {
text: 'Vol',
},
top: '62%',
height: '18%',
offset: 0,
lineWidth: 2
}, {
title: {
text: 'Asset',
},
top: '82%',
height: '18%',
offset: 0,
lineWidth: 2
}],
series: [{
name: 'PnL',
data: [],
id: 'primary',
tooltip: {
xDateFormat: '%Y-%m-%d %H:%M:%S'
},
yAxis: 0
}, {
type: 'column',
lineWidth: 2,
name: 'Profit',
data: [],
yAxis: 1,
}, {
type: 'column',
name: 'Trade',
data: [],
yAxis: 2
}, {
type: 'area',
step: true,
lineWidth: 0,
name: 'Long',
data: [],
yAxis: 2
}, {
type: 'area',
step: true,
lineWidth: 0,
name: 'Short',
data: [],
yAxis: 2
}, {
type: 'line',
step: true,
color: '#5b4b00',
name: 'Asset',
data: [],
yAxis: 3
}, {
type: 'pie',
innerSize: '70%',
name: 'Random',
data: [],
center: ['3%', '6%'],
size: '15%',
dataLabels: {
enabled: false
},
startAngle: -90,
endAngle: 90,
}],
};
function main() {
let c = Chart(chartCfg);
let preTicker = null;
while (true) {
let t = exchange.GetTicker();
c.add(0, [t.Time, t.Last]); // PnL
c.add(1, [t.Time, preTicker ? t.Last - preTicker.Last : 0]); // profit
let r = Math.random();
var pos = parseInt(t.Time/86400);
c.add(2, [t.Time, pos/2]); // Vol
c.add(3, [t.Time, r > 0.8 ? pos : null]); // Long
c.add(4, [t.Time, r < 0.8 ? -pos : null]); // Short
c.add(5, [t.Time, Math.random() * 100]); // Asset
// update pie
chartCfg.series[chartCfg.series.length-1].data = [
["A", Math.random()*100],
["B", Math.random()*100],
];
c.update(chartCfg)
preTicker = t;
}
}
python
'''backtest
start: 2020-03-11 00:00:00
end: 2020-04-09 23:59:00
period: 1d
exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}]
'''
import random
chartCfg = {
"subtitle": {
"text": "subtitle"
},
"yAxis": [{
"height": "40%",
"lineWidth": 2,
"title": {
"text": 'PnL'
},
"tickPixelInterval": 20,
"minorGridLineWidth": 1,
"minorTickWidth": 0,
"opposite": True,
"labels": {
"align": "right",
"x": -3
}
}, {
"title": {
"text": 'Profit'
},
"top": "42%",
"height": "18%",
"offset": 0,
"lineWidth": 2
}, {
"title": {
"text": 'Vol'
},
"top": '62%',
"height": '18%',
"offset": 0,
"lineWidth": 2
}, {
"title": {
"text": 'Asset'
},
"top": '82%',
"height": '18%',
"offset": 0,
"lineWidth": 2
}],
"series": [{
"name": 'PnL',
"data": [],
"id": 'primary',
"tooltip": {
"xDateFormat": '%Y-%m-%d %H:%M:%S'
},
"yAxis": 0
}, {
"type": 'column',
"lineWidth": 2,
"name": 'Profit',
"data": [],
"yAxis": 1
}, {
"type": 'column',
"name": 'Trade',
"data": [],
"yAxis": 2
}, {
"type": 'area',
"step": True,
"lineWidth": 0,
"name": 'Long',
"data": [],
"yAxis": 2
}, {
"type": 'area',
"step": True,
"lineWidth": 0,
"name": 'Short',
"data": [],
"yAxis": 2
}, {
"type": 'line',
"step": True,
"color": '#5b4b00',
"name": 'Asset',
"data": [],
"yAxis": 3
}, {
"type": 'pie',
"innerSize": '70%',
"name": 'Random',
"data": [],
"center": ['3%', '6%'],
"size": '15%',
"dataLabels": {
"enabled": False
},
"startAngle": -90,
"endAngle": 90
}]
}
def main():
c = Chart(chartCfg)
preTicker = None
while True:
t = exchange.GetTicker()
c.add(0, [t["Time"], t["Last"]])
profit = t["Last"] - preTicker["Last"] if preTicker else 0
c.add(1, [t["Time"], profit])
r = random.random()
pos = t["Time"] / 86400
c.add(2, [t["Time"], pos / 2])
long = pos if r > 0.8 else None
c.add(3, [t["Time"], long])
short = -pos if r < 0.8 else None
c.add(4, [t["Time"], short])
c.add(5, [t["Time"], random.random() * 100])
# update pie
chartCfg["series"][len(chartCfg["series"]) - 1]["data"] = [
["A", random.random() * 100],
["B", random.random() * 100]
]
c.update(chartCfg)
preTicker = t
c++
/*backtest
start: 2020-03-11 00:00:00
end: 2020-04-09 23:59:00
period: 1d
exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}]
*/
void main() {
json chartCfg = R"({
"subtitle": {
"text": "subtitle"
},
"yAxis": [{
"height": "40%",
"lineWidth": 2,
"title": {
"text": "PnL"
},
"tickPixelInterval": 20,
"minorGridLineWidth": 1,
"minorTickWidth": 0,
"opposite": true,
"labels": {
"align": "right",
"x": -3
}
}, {
"title": {
"text": "Profit"
},
"top": "42%",
"height": "18%",
"offset": 0,
"lineWidth": 2
}, {
"title": {
"text": "Vol"
},
"top": "62%",
"height": "18%",
"offset": 0,
"lineWidth": 2
}, {
"title": {
"text": "Asset"
},
"top": "82%",
"height": "18%",
"offset": 0,
"lineWidth": 2
}],
"series": [{
"name": "PnL",
"data": [],
"id": "primary",
"tooltip": {
"xDateFormat": "%Y-%m-%d %H:%M:%S"
},
"yAxis": 0
}, {
"type": "column",
"lineWidth": 2,
"name": "Profit",
"data": [],
"yAxis": 1
}, {
"type": "column",
"name": "Trade",
"data": [],
"yAxis": 2
}, {
"type": "area",
"step": true,
"lineWidth": 0,
"name": "Long",
"data": [],
"yAxis": 2
}, {
"type": "area",
"step": true,
"lineWidth": 0,
"name": "Short",
"data": [],
"yAxis": 2
}, {
"type": "line",
"step": true,
"color": "#5b4b00",
"name": "Asset",
"data": [],
"yAxis": 3
}, {
"type": "pie",
"innerSize": "70%",
"name": "Random",
"data": [],
"center": ["3%", "6%"],
"size": "15%",
"dataLabels": {
"enabled": false
},
"startAngle": -90,
"endAngle": 90
}]
})"_json;
Chart c = Chart(chartCfg);
Ticker preTicker;
while(true) {
auto t = exchange.GetTicker();
c.add(0, {t.Time, t.Last});
auto profit = preTicker.Valid ? t.Last - preTicker.Last : 0;
c.add(1, {t.Time, profit});
auto r = rand() % 100;
auto pos = t.Time / 86400.0;
c.add(2, {t.Time, pos / 2.0});
auto longPos = r > 0.8 ? pos : NULL;
c.add(3, {t.Time, longPos});
auto shortPos = r < 0.8 ? -pos : NULL;
c.add(4, {t.Time, shortPos});
c.add(5, {t.Time, rand() % 100});
// update pie
json pie = R"([["A", 0], ["B", 0]])"_json;
pie[0][1] = rand() % 100;
pie[1][1] = rand() % 100;
chartCfg["series"][chartCfg["series"].size() - 1]["data"] = pie;
c.update(chartCfg);
preTicker = t;
}
}
Among the charts, the pie chart is a chart without time axis, and the chart configuration needs to be updated directly when updating data. For example, the code in the above example, after updating the data, use c.update(chartCfg) to update the chart as follows:
javascript
// update pie
chartCfg.series[chartCfg.series.length-1].data = [
["A", Math.random()*100],
["B", Math.random()*100],
];
c.update(chartCfg)
python
# update pie
chartCfg["series"][len(chartCfg["series"]) - 1]["data"] = [
["A", random.random() * 100],
["B", random.random() * 100]
]
c.update(chartCfg)
c++
// update pie
json pie = R"([["A", 0], ["B", 0]])"_json;
pie[0][1] = rand() % 100;
pie[1][1] = rand() % 100;
chartCfg["series"][chartCfg["series"].size() - 1]["data"] = pie;
c.update(chartCfg);
KLineChart(...)
KLineChart(chartCfg), this function is used to use a drawing method similar to Pine language for custom drawing when the strategy is running. The strategy custom drawing can only use the KLineChart() method or one of the Chart() methods.
Code for reference:
javascript
function main() {
// Call the KLineChart function to create a chart control object c
let c = KLineChart({
overlay: true
})
// Use the spot exchange object test to obtain K-line data. If you use the futures exchange object test, you need to set up the contract first.
let bars = exchange.GetRecords()
if (!bars) {
return
}
bars.forEach(function(bar, index) {
c.begin(bar)
c.barcolor(bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.2)')
if (bar.Close > bar.Open) {
c.bgcolor('rgba(0, 255, 0, 0.5)')
}
let h = c.plot(bar.High, 'high')
let l = c.plot(bar.Low, 'low')
c.fill(h, l, {
color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)'
})
c.hline(bar.High)
c.plotarrow(bar.Close - bar.Open)
c.plotshape(bar.Low, {
style: 'diamond'
})
c.plotchar(bar.Close, {
char: 'X'
})
c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)
if (bar.Close > bar.Open) {
// long/short/closelong/closeshort
c.signal("long", bar.High, 1.5)
} else if (bar.Close < bar.Open) {
c.signal("closelong", bar.Low, 1.5)
}
c.close()
})
}
python
def main():
# Call the KLineChart function to create a chart control object c
c = KLineChart({
"overlay": True
})
# Use the spot exchange object test to obtain K-line data. If you use the futures exchange object test, you need to set up the contract first.
bars = exchange.GetRecords()
if not bars:
return
for bar in bars:
c.begin(bar)
c.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)')
if bar.Close > bar.Open:
c.bgcolor('rgba(0, 255, 0, 0.5)')
h = c.plot(bar.High, 'high')
l = c.plot(bar.Low, 'low')
c.fill(h, l, 'rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(255, 0, 0, 0.2)')
c.hline(bar.High)
c.plotarrow(bar.Close - bar.Open)
c.plotshape(bar.Low, style = 'diamond')
c.plotchar(bar.Close, char = 'X')
c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)
if bar.Close > bar.Open:
# long/short/closelong/closeshort
c.signal("long", bar.High, 1.5)
elif bar.Close < bar.Open:
c.signal("closelong", bar.Low, 1.5)
c.close()
c++
// Not supported currently
If there must be a chart control object for drawing in the custom drawing area of the strategy, use the function KLineChart to create this object. The parameter of the KLineChart function is a chart configuration structure, the chart structure used in the reference code is very simple {overlay: true}). This chart configuration structure only sets the drawing content to be output on the main chart. If overlay is set to false, the content on the chart is output on the sub-chart. If you need to specify a drawing function to draw on the main chart, you can also specify the parameter overlay as true in the specific function call.
The drawing operation is executed by traversing over the K-line data. The drawing operation must start with a c.begin(bar) function call and end with a c.close() function call. The drawing interface functions of the Pine language supported in the drawing operation are:
-
barcolor: Set K-line color
barcolor(color, offset, editable, show_last, title, display)
javascriptc.barcolor(bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.2)') // Use the example illustrated in the reference code in this example, without giving unnecessary detailspythonc.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)')displayoptional parameters: "none", "all"
-
bgcolor: Fill the background of the K-line with the specified color
bgcolor(color, offset, editable, show_last, title, display, overlay)
javascriptc.bgcolor('rgba(0, 255, 0, 0.5)')pythonc.bgcolor('rgba(0, 255, 0, 0.5)')displayOptional parameters: "none", "all"
-
plot: Plot a series of data on the chart
plot(series, title, color, linewidth, style, trackprice, histbase, offset, join, editable, show_last, display)
javascriptc.plot(bar.High, 'high')pythonh = c.plot(bar.High, 'high')styleoptional parameters: "stepline_diamond", "stepline", "cross", "areabr", "area", "circles", "columns", "histogram", "linebr", "line"displayoptional parameters: "none", "all"
-
fill: Fill the background between two drawings or hlines with the provided color
fill(hline1, hline2, color, title, editable, fillgaps, display)
javascriptlet h = c.plot(bar.High, 'high') let l = c.plot(bar.Low, 'low') c.fill(h, l, {color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)'})pythonh = c.plot(bar.High, 'high') l = c.plot(bar.Low, 'low') c.fill(h, l, {"color": 'rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(255, 0, 0, 0.2)'})displayoptional parameters: "none", "all"
Since the
JavaScriptlanguage cannot specify the incoming parameters according to the names of function formal parameters, in order to solve this problem, a{key: value}structure can be used to specify the parameter passed in a certain formal parameter name, for example, in the reference code, use{color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)'}to specify thecolorparameter of thefillfunction. If you need to specify parameters with multiple parameter names consecutively, you can use{key1: value1, key2: value2, key3: value3}. For example, in this example, add a parameter specifying atitle:{color: bar.Close > bar.Open ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)', title: 'fill'}.For the color value, you can use the
'rgba(255, 0, 0, 0.2)'method to set, or use the'#FF0000'method to set. -
hline: Renders a horizontal line at a given fixed price level
hline(price, title, color, linestyle, linewidth, editable, display)
javascriptc.hline(bar.High)pythonc.hline(bar.High)linestyleoptional parameters: "dashed", "dotted", "solid"displayoptional parameters: "none", "all"
-
plotarrow: Draw up and down arrows on the chart
plotarrow(series, title, colorup, colordown, offset, minheight, maxheight, editable, show_last, display)
javascriptc.plotarrow(bar.Close - bar.Open)pythonc.plotarrow(bar.Close - bar.Open)displayoptional parameters: "none", "all"
-
plotshape: Draw visual shapes on the chart
plotshape(series, title, style, location, color, offset, text, textcolor, editable, size, show_last, display)
javascriptc.plotshape(bar.Low, {style: 'diamond'})pythonc.plotshape(bar.Low, style = 'diamond')-
styleoptional parameters: "diamond", "square", "label_down", "label_up", "arrow_down", "arrow_up", "circle", "flag", "triangle_down", "triangle_up", "cross", "xcross" -
locationoptional parameters: "abovebar", "belowbar", "top", "bottom", "absolute" -
sizeoptional parameters: "10px", "14px", "20px", "40px", "80px", compared with thesize.tiny,size.small,size.normal,size.large,size.hugein Pine language
size.autoissize.small. -
displayoptional parameters: "none", "all"
-
-
plotchar: Draw visible shapes on the chart by using any given Unicode character
plotchar(series, title, char, location, color, offset, text, textcolor, editable, size, show_last, display)
javascriptc.plotchar(bar.Close, {char: 'X'})pythonc.plotchar(bar.Close, char = 'X')-
locationoptional parameters: "abovebar", "belowbar", "top", "bottom", "absolute" -
sizeoptional parameters: "10px", "14px", "20px", "40px", "80px", compared with thesize.tiny,size.small,size.normal,size.large,size.hugein Pine language
size.autoissize.small. -
displayoptional parameters: "none", "all"
-
-
plotcandle: Draw a K-line chart on the chart
plotcandle(open, high, low, close, title, color, wickcolor, editable, show_last, bordercolor, display)
javascriptc.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)pythonc.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)displayOptional parameters: "none", "all"
-
signal: Functions that are not available in the Pine language are used here to draw buying and selling signals
signal(direction, price, qty, id)
javascriptc.signal("long", bar.High, 1.5)pythonc.signal("long", bar.High, 1.5)The input parameter
"long"indicates the transaction direction, optional"long","closelong","short","closeshort". The incoming parameterbar.Highis the Y-axis position of the marker signal. The input parameter1.5represents the number of trades of the signal. The fourth parameter can be passed in to replace the text content drawn by default. The default text of the drawn signal marker is the trading direction, for example:"closelong".
For some color, style and other settings used in the above function calls, please refer to Special article on drawing with KLineChart function
LogReset()
LogReset() is used to clear logs. You can pass in an integer parameter to specify the number of recent logs to retain, and clear the rest of the logs. The startup log is counted every time it is started, so if no parameters are passed in, and there is no log output at the start of the strategy, the log will not be displayed at all, waiting for the docker log to return (not an abnormal situation). The function has no return value.
javascript
function main() {
// Maintain the last 10 logs and clear the rest
LogReset(10)
}
python
def main():
LogReset(10)
c++
void main() {
LogReset(10);
}
LogVacuum()
LogVacuum(), after calling the LogReset() function to clear the log, recovers the storage space occupied by SQLite when deleting data. The function has no return value.
The reason is that SQLite does not reclaim the occupied space when deleting data, so you need to perform VACUUM to clean up the table, and release the space. When this function is called, the file move operation will occur with a large delay. It is recommended to call it at an appropriate time interval.
Market Quotes API
The main market interface functions:
| Function Name | Description |
|---|---|
| GetTicker | Get tick quotes data |
| GetRecords | Get K-line data |
| GetDepth | Get order book data (order depth data) |
| GetTrades | Get the latest trading records in the market |
The following functions can be called through exchange or exchanges[0] objects; for example: functions, likeexchange.GetTicker(); or exchanges[0].GetTicker();, return the market quotes of the current trading pairs and setting contracts.
Important tips for calling API functions with network access:
When calling any API function that accesses a platform interface (such as exchange.GetTicker(), exchange.Buy(Price, Amount), exchange.CancelOrder(Id), etc.), the failure of access is probably caused by various reasons. Therefore, we must do fault-tolerant processing for the call of these functions. For example: the exchange.GetTicker() function for obtaining market data may, due to platform server problems and network transmission problems, etc., cause result that the return value of the exchange.GetTicker() function is null. Then, the return value of exchange.GetTicker() must be dealt with by fault-tolerant processing.
The data returned by the exchange.GetTicker() function in the following code is assigned to the ticker variable, and we need to deal with fault tolerance before using the ticker variable.
javascript
function main() {
var ticker = exchange.GetTicker()
if(!ticker){
// Recall once, or use other processing logic
ticker = exchange.GetTicker()
}
}
python
def main():
ticker = exchange.GetTicker()
if not ticker:
ticker = exchange.GetTicker()
c++
void main() {
auto ticker = exchange.GetTicker();
if(!ticker.Valid) {
ticker = exchange.GetTicker();
Log("Test");
}
}
In addition, for the fault-tolerant performance testing of the strategy, FMZ has specifically added a unique fault-tolerant mode backtest. The backtest system can randomly return some API calls that will occur when the network is accessed according to the set parameters, and return the return values of some failed calls. You can quickly test the robustness of the program in the bot.
Switch to the backtest system page on the strategy editing page, click the inverted triangle drop-down control on the right side of the "Start Backtest" button, and the "Backtest Fault Tolerant" button will pop up.
exchange.GetTicker()
exchange.GetTicker() gets the current market quotes of current trading pairs and contracts, with the return value: Ticker structure.
In the backtest system, the Ticker data returned by the exchange.GetTicker() function, where High and Low are simulated values, taken from current time of "sell 1" and "buy 1" in the bot. The cryptocurrency in the real running is the highest price and the lowest price in a certain period defined by the exchange Tick interface.
javascript
function main(){
var ticker = exchange.GetTicker()
/*
The platform interface may not be accessible due to network problems (even if the device's docker program can open the platform website, the API may not be accessible)
At this time, ticker is null, when accessing ticker. When it is "High", it will cause an error; so when testing, ensure that you can access the platform interface
*/
Log("High:", ticker.High, "Low:", ticker.Low, "Sell:", ticker.Sell, "Buy:", ticker.Buy, "Last:", ticker.Last, "Volume:", ticker.Volume)
}
python
def main():
ticker = exchange.GetTicker()
Log("High:", ticker["High"], "Low:", ticker["Low"], "Sell:", ticker["Sell"], "Buy:", ticker["Buy"], "Last:", ticker["Last"], "Volume:", ticker["Volume"])
c++
void main() {
auto ticker = exchange.GetTicker();
Log("High:", ticker.High, "Low:", ticker.Low, "Sell:", ticker.Sell, "Buy:", ticker.Buy, "Last:", ticker.Last, "Volume:", ticker.Volume);
}
In the real bot (not backtest), the Info attribute in the return value of the exchange.GetTicker() function stores the original data returned when the interface is called.
exchange.GetDepth()
exchange.GetDepth() gets the exchange order book data of current trading pairs and contracts. Return value: Depth structure.
Depth structure contains two arrays of structures, namely Asks[] and Bids[], Asks and Bids contain the following structure variables:
| Data Type | Variable Name | Description |
|---|---|---|
| number | Price | price |
| number | Amount | amount |
For example, if I want to get the current sell second price, I can write the code like this:
javascript
function main(){
var depth = exchange.GetDepth()
/*
The platform interface may not be accessible due to network reasons (even if the device's docker program can open the platform website, the API may not be accessible)
At this time, depth is null. When accessing depth.Asks[1].Price, it will cause an error; so when testing, ensure that you can access the platform interface
*/
var price = depth.Asks[1].Price
Log("Sell 2 price is:", price)
}
python
def main():
depth = exchange.GetDepth()
price = depth["Asks"][1]["Price"]
Log("Sell 2 price is:", price)
c++
void main() {
auto depth = exchange.GetDepth();
auto price = depth.Asks[1].Price;
Log("Sell 2 price is:", price);
}
exchange.GetTrades()
exchange.GetTrades() gets platform trading history (not your own). Return value: Trade structure array. Some exchanges do not support it. The specific data returned depends on the trading records within the range, according to specific circumstances.
The returned data is an array, where the time sequence of each element is the same as the return data sequence of the exchange.GetRecords function, that is, the last element of the array is the data closest to the current time.
javascript
// In the simulated backtest, the data is empty; to have a trading history, there must be a real bot running
function main(){
var trades = exchange.GetTrades()
/*
The platform interface may not be accessible due to network reasons (even if the device's docker program can open the platform website, the API may not be accessible)
At this time, "trades" is null. When accessing trades[0].Id, it will cause an error; so when testing, ensure that you can access the platform interface
*/
Log("id:", trades[0].Id, "time:", trades[0].Time, "Price:", trades[0].Price, "Amount:", trades[0].Amount, "type:", trades[0].Type)
}
python
def main():
trades = exchange.GetTrades()
Log("id:", trades[0]["Id"], "time:", trades[0]["Time"], "Price:", trades[0]["Price"], "Amount:", trades[0]["Amount"], "type:", trades[0]["Type"])
c++
void main() {
auto trades = exchange.GetTrades();
Log("id:", trades[0].Id, "time:", trades[0].Time, "Price:", trades[0].Price, "Amount:", trades[0].Amount, "type:", trades[0].Type);
}
exchange.GetRecords()
exchange.GetRecords(Period) returns K-line data. K-line period is specified when creating the bot; if you specify the parameters when the exchange.GetRecords() function is called, the obtained data will be the K-line data corresponding to the parameter period. If there is no specified parameter, the K-line data is returned according to the K-line period set on the bot parameters or the K-line period set on the backtest page.
Parameter Period:
- PERIOD_M1: refers to 1 minute
- PERIOD_M5: refers to 5 minutes
- PERIOD_M15: refers to 15 minutes
- PERIOD_M30: refers to 30 minutes
- PERIOD_H1: refers to 1 hour
- PERIOD_D1: refers to 1 day
The parameter value ofPeriodonly can pass the standard periods defined above, but also can pass numbers, in the unit of second.
The return value of exchange.GetRecords(Period) function:
The return value is an array of Record structures, the returned K-line data will be accumulated over time, the upper limit of the accumulated K-line bars is affected by the exchange.SetMaxBarLen function setting. The default upper limit is 5000 K-line bars when it is not set. When the K-Line data reaches the K-Line bar accumulation limit, it will be updated by adding a K-Line bar and deleting the earliest K-Line bar (e.g. queue in/out). Some exchanges do not provide a K-line interface, then the docker collects the market transaction record data in real time to generate K-lines.
Number of K-line bars fetched when the GetRecords function is initially called.
- The first 1000 K-line bars at the starting moment of the backtesting period are taken in advance in the backtesting system as the initial K-line data.
- The specific number of K-line bars to be acquired during real bot is based on the maximum amount of data that can be acquired by the K-line interface of the exchange.
For the exchange.GetRecords(Period) function, the bot and backtest of cryptocurrency both support custom periods, and the parameter Period is the number of seconds. For example:
javascript
function main() {
// Print K-line data with a K-line period of 120 seconds (2 minutes)
Log(exchange.GetRecords(60 * 2))
// Print K-line data with a K-line period of 5 minutes
Log(exchange.GetRecords(PERIOD_M5))
}
python
def main():
Log(exchange.GetRecords(60 * 2))
Log(exchange.GetRecords(PERIOD_M5))
c++
void main() {
Log(exchange.GetRecords(60 * 2)[0]);
Log(exchange.GetRecords(PERIOD_M5)[0]);
}
Setting Period parameter to 5 is to request K-line data with a period of 5 seconds.
If the Period parameter is not evenly divided by 60 (that is, the period represented is a period of unavailable minutes), the bottom layer of the system uses the relevant interface of GetTrades to obtain trading record data and synthesize the required K-line data.
If the Period parameter is evenly divided by 60, then the required K-line data is synthesized by using a minimum of 1-minute K-line data (using a larger period to synthesize the required K-line data if possible).
javascript
function main(){
var records = exchange.GetRecords(PERIOD_H1)
/*
The platform interface may not be accessible due to network reasons (even if the device's docker program can open the platform website, the API may not be accessible)
At this time, "records" is null. When accessing records[0].Time, it will cause an error; so when testing, ensure that you can access the platform interface
*/
Log("The first k-line data is, Time:", records[0].Time, "Open:", records[0].Open, "High:", records[0].High)
Log("The second k-line data is, Time:", records[1].Time ,"Close:", records[1].Close)
Log("Current K-line (latest)", records[records.length-1], "Current K-line (latest)", records[records.length-2])
}
python
def main():
records = exchange.GetRecords(PERIOD_H1)
Log("The first k-line data is, Time:", records[0]["Time"], "Open:", records[0]["Open"], "High:", records[0]["High"])
Log("The second k-line data is, Time:", records[1]["Time"], "Close:", records[1]["Close"])
Log("Current K-line (latest)", records[-1], "Current K-line (latest)", records[-2])
c++
void main() {
auto records = exchange.GetRecords(PERIOD_H1);
Log("The first k-line data is, Time:", records[0].Time, "Open:", records[0].Open, "High:", records[0].High);
Log("The second k-line data is, Time:", records[1].Time, "Close:", records[1].Close);
Log("Current K-line (latest)", records[records.size() - 1], "Current K-line (latest)", records[records.size() - 2]);
}
exchange.GetRecords() function has two conditions for obtaining K-line data:
-
The exchange provides a K-line data interface. In this case, the acquired data is the data directly returned by the exchange.
-
The exchange does not provide a K-line data interface. The FMZ docker program obtains the latest trading records of the exchange each time the strategy program calls
exchange.GetRecords(), that is, it calls theexchange.GetTrades()function to obtain data and synthesize K-line data.
The simulation-level backtest in the system needs to set the underlying K-line period (when the backtesting system simulates the level of back-testing, the corresponding K-line data is used to generate tick data according to the set underlying K-line period); it should be noted that the period of the K-line data obtained in the strategy cannot be less than the underlying K-line period. Because in the simulation-level backtest, the K-line data of each period is synthesized by the K-line data corresponding to the underlying K-line periods in the backtest system.
In cpp language, if you need to construct your own K-line data, there are the following code examples:
c++
#include <sstream>
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);
}
// Output display: Records[10]
Log(r);
auto ma = TA.MA(r,10);
// Output display: [nan,nan,nan,nan,nan,nan,nan,nan,nan,450]
Log(ma);
}
exchange.GetPeriod()
The exchange.GetPeriod() function returns the K-line period set on the FMZ platform website page when running strategies in backtest and bot. The return value is an integer in the unit of second. Return value: numeric type.
javascript
function main() {
// For example, in the backtest and bot, set the K-line period on the website page of FMZ platform to 1 hour
var period = exchange.GetPeriod()
Log("K-line period:", period / (60 * 60), "hour")
}
python
def main():
period = exchange.GetPeriod()
Log("K-line period:", period / (60 * 60), "hour")
c++
void main() {
auto period = exchange.GetPeriod();
Log("K-line period:", period / (60 * 60.0), "hour");
}
exchange.SetMaxBarLen(Len)
The exchange.SetMaxBarLen(Len) function affects two aspects during the runtime of the cryptocurrency strategy:
- Affecting the number of K-line bars (BAR) obtained for the first time.
- Affecting the upper limit of K-line bars (BAR).
javascript
function main() {
exchange.SetMaxBarLen(50)
var records = exchange.GetRecords()
Log(records.length, records)
}
python
def main():
exchange.SetMaxBarLen(50)
r = exchange.GetRecords()
Log(len(r), r)
c++
void main() {
exchange.SetMaxBarLen(50);
auto r = exchange.GetRecords();
Log(r.size(), r[0]);
}
exchange.GetRawJSON()
exchange.GetRawJSON() returns the raw content (strings) returned by the last REST request, which can be used to parse data by yourself. Return value: string type, only valid in the cryptocurrency live trading environment. Backtest does not support the function.
Strategies in cpp language do not support the function.
javascript
function main(){
exchange.GetAccount();
var obj = JSON.parse(exchange.GetRawJSON());
Log(obj);
}
python
import json
def main():
exchange.GetAccount()
obj = json.loads(exchange.GetRawJSON())
Log(obj)
c++
void main() {
auto obj = exchange.GetAccount();
// C++ doe not support "GetRawJSON" function
Log(obj);
}
exchange.GetRate()
exchange.GetRate() returns the exchange rates of the currency currently used in the exchange and the currently displayed pricing currency, and a return value of 1 indicates that exchange rate conversion is disabled. Return value: numeric type.
Note:
- If
exchange.SetRate()has not been called to set exchange rate, the exchange rate value returned byexchange.GetRate()is 1 by default, that is, the exchange rate conversion currently displayed has not been exchanged. - If the
exchange.SetRate()has been used to set an exchange rate value, such as,exchange.SetRate(7), then all price information of the current exchange object representing the circulating currency of the trading platform, such as quotes, depth, order prices, etc., will be multiplied by the previously set exchange rate 7 for conversion. - If the
exchangecorresponds to an exchange with USD as the pricing currency, after callingexchange.SetRate(7), all prices of the bot will be converted to prices close to CNY by multiplying by 7. At this time, the exchange rate value obtained by usingexchange.GetRate()is 7.
exchange.GetUSDCNY()
exchange.GetUSDCNY() returns the latest exchange rate of the US dollar (data source provided by yahoo). The return value: numeric type.
exchange.SetData(Key, Value)
The exchange.SetData(Key, Value) function is used to set the data loaded at the time when the strategy is running, which can be any economic indicator, industry data, relevant index, etc. It can be used to quantify all quantifiable information for trading strategies and also support using in the backtest system.
Calling method of exchange.SetData(Key, Value) function:
-
Write data directly into the strategy
The data format is required as thedatavariable in the following example.javascript/*backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ function main() { var data = [ [1579536000000, "abc"], [1579622400000, 123], [1579708800000, {"price": 123}], [1579795200000, ["abc", 123, {"price": 123}]] ] exchange.SetData("test", data) while(true) { Log(exchange.GetData("test")) Sleep(1000) } }python'''backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] ''' def main(): data = [ [1579536000000, "abc"], [1579622400000, 123], [1579708800000, {"price": 123}], [1579795200000, ["abc", 123, {"price": 123}]] ] exchange.SetData("test", data) while True: Log(exchange.GetData("test")) Sleep(1000)c++/*backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ void main() { json data = R"([ [1579536000000, "abc"], [1579622400000, 123], [1579708800000, {"price": 123}], [1579795200000, ["abc", 123, {"price": 123}]] ])"_json; exchange.SetData("test", data); while(true) { Log(exchange.GetData("test")); Sleep(1000); } }When the above test code is run, the corresponding data will be obtained at the corresponding time, as shown in the figure:
As we can see, the corresponding time of timestamp
1579622400000is2020-01-22 00: 00: 00; when the strategy program runs after this time, before the next data timestamp1579708800000, that is, before the time of2020-01-23 00: 00: 00, call theexchange.GetData(Source)function to get the data. All obtained is the content of[1579622400000, 123]. As the program continues to run and time changes, obtain data piece by piece data like this. -
Request data through external links
Requested data format
javascript{ "schema":["time","data"], "data":[ [1579536000000, "abc"], [1579622400000, 123], [1579708800000, {"price": 123}], [1579795200000, ["abc", 123, {"price": 123}]] ] }Where
schemais the data format of each record in the main body of the loaded data, the format is fixed as["time", "data"], corresponding to thedataattribute format of the data one by one. Thedataattribute stores the main body of the data, and each piece of the data is composed of millisecond-level timestamps and data content (the data content can be any JSON encoded data).The service program for testing is written in
Golanguage:golangpackage 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","data"}, "data": []interface{}{ []interface{}{1579536000000, "abc"}, []interface{}{1579622400000, 123}, []interface{}{1579708800000, map[string]interface{}{"price":123}}, []interface{}{1579795200000, []interface{}{"abc", 123, map[string]interface{}{"price":123}}}, }, } b, _ := json.Marshal(ret) w.Write(b) }() } func main () { fmt.Println("listen http://localhost:9090") http.HandleFunc("/data", Handle) http.ListenAndServe(":9090", nil) }After receiving the request, the program responds to the data:
javascript{ "schema":["time","data"], "data":[ [1579536000000, "abc"], [1579622400000, 123], [1579708800000, {"price": 123}], [1579795200000, ["abc", 123, {"price": 123}]] ] }Test strategy code:
javascript/*backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ function main() { while(true) { Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")) Sleep(1000) } }python'''backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] ''' def main(): while True: Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")) Sleep(1000)c++/*backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ void main() { while(true) { Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")); Sleep(1000); } }
exchange.GetData(Source)
The exchange.GetData(Source) function is used to get the data loaded by the exchange.SetData(Key, Value) function or the data provided by the external link, it can be used in the backtest system. The data is obtained at one time during the backtest, and the data is cached for one minute during the actual trading.
-
Obtaining the calling method of data written directly
javascript/*backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ function main() { exchange.SetData("test", [[1579536000000, _D(1579536000000)], [1579622400000, _D(1579622400000)], [1579708800000, _D(1579708800000)]]) while(true) { Log(exchange.GetData("test")) Sleep(1000 * 60 * 60 * 24) } }python'''backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] ''' def main(): exchange.SetData("test", [[1579536000000, _D(1579536000000/1000)], [1579622400000, _D(1579622400000/1000)], [1579708800000, _D(1579708800000/1000)]]) while True: Log(exchange.GetData("test")) Sleep(1000 * 60 * 60 * 24)c++/*backtest start: 2020-01-21 00:00:00 end: 2020-02-12 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}] */ void main() { json arr = R"([[1579536000000, ""], [1579622400000, ""], [1579708800000, ""]])"_json; arr[0][1] = _D(1579536000000); arr[1][1] = _D(1579622400000); arr[2][1] = _D(1579708800000); exchange.SetData("test", arr); while(true) { Log(exchange.GetData("test")); Sleep(1000 * 60 * 60 * 24); } } -
Method of calling data from external links
javascriptfunction main() { Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")) Log(exchange.GetData("https://www.fmz.com/upload/asset/32bf73a69fc12d36e76.json")) }pythondef main(): Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")) Log(exchange.GetData("https://www.fmz.com/upload/asset/32bf73a69fc12d36e76.json"))c++void main() { Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")); Log(exchange.GetData("https://www.fmz.com/upload/asset/32bf73a69fc12d36e76.json")); } -
Use the fundamental data of the platform data center
Use theexchange.GetData(Source)function to get Fundamental Data.
When calling the exchange.GetData(Source) function, you can pass in the second parameter to set the cache timeout in milliseconds. The default is one-minute cache timeout in real bot. In the backtest system, when using the access interface to request data, the backtest system will add parameters "from" (timestamp seconds), to (timestamp seconds), period (underlying K-line period, timestamp milliseconds) and other parameters automatically to the request, to determine the time range of the data to be obtained.
Trading API
The following functions can be called through the exchange or exchanges[0] object. For example: exchange.Sell(100, 1); indicates the next order is a sell order with a price of 100 and a quantity of 1 on the exchange.
exchange.Buy(Price, Amount)
exchange.Buy(Price, Amount) is used to place a buy order and return an order ID. Parameter value: Price is the order price in type of number, and Amount is the order amount in type of number. Return value: string type or numeric type (the specific type depends on the return type of each exchange platform).
The returned order number can be used to query order information and cancel orders.
javascript
function main() {
var id = exchange.Buy(100, 1);
Log("id:", id);
}
python
def main():
id = exchange.Buy(100, 1)
Log("id:", id)
c++
void main() {
auto id = exchange.Buy(100, 1);
Log("id:", id);
}
-
Futures orders
When placing orders for futures, we must pay attention to whether the trading direction is set correctly. If the trading direction and trading function do not match, an error will be reported. The number of orders placed on a cryptocurrency futures exchange is the number of contracts unless otherwise specified.
For example:javascript// The following is the wrong call function main() { exchange.SetContractType("quarter") // Set short direction exchange.SetDirection("sell") // Place a buy order, and you will get an error, so you can only sell short var id = exchange.Buy(50, 1) // Set short direction exchange.SetDirection("buy") // Place a sell order, and you will get an error, so you can only buy long var id2 = exchange.Sell(60, 1) // Set close long position direction exchange.SetDirection("closebuy") // Place a buy order, and you will get an error, so you can only sell short var id3 = exchange.Buy(-1, 1) // Set close short position direction exchange.SetDirection("closesell") // Place a sell order, and you will get an error, so you can only buy long var id4 = exchange.Sell(-1, 1) }python# The following is the wrong call def main(): exchange.SetContractType("quarter") exchange.SetDirection("sell") id = exchange.Buy(50, 1) exchange.SetDirection("buy") id2 = exchange.Sell(60, 1) exchange.SetDirection("closebuy") id3 = exchange.Buy(-1, 1) exchange.SetDirection("closesell") id4 = exchange.Sell(-1, 1)c++// The following is the wrong call void main() { exchange.SetContractType("quarter"); exchange.SetDirection("sell"); auto id = exchange.Buy(50, 1); exchange.SetDirection("buy"); auto id2 = exchange.Sell(60, 1); exchange.SetDirection("closebuy"); auto id3 = exchange.Buy(-1, 1); exchange.SetDirection("closesell"); auto id4 = exchange.Sell(-1, 1); }Error messages:
direction is sell, invalid order type Buy direction is buy, invalid order type Sell direction is closebuy, invalid order type Buy direction is closesell, invalid order type Sell -
Market order
Note: The exchange order interface is required to support market orders (when the order type is a buy order, the order amount parameter is the amount in quote currency), and the market order method of cryptocurrency futures is used to place orders, and the unit of the quantity parameter is the number of contracts. A few live trading exchanges for digital currencies do not support market order interfaces.
javascript// For example, trading pairs: ETH_BTC, bought in by market order function main() { // Buy a market order, and buy ETH coins equal to 0.1 BTC (quote currency) exchange.Buy(-1, 0.1) }pythondef main(): exchange.Buy(-1, 0.1)c++void main() { exchange.Buy(-1, 0.1); }
exchange.Sell(Price, Amount)
exchange.Sell(Price, Amount) places a sell order and returns an order ID. Parameter value: Price is the order price, numeric type. Amount is the order amount, numeric type. Return value: string type or numeric type (the specific type depends on the return type of each exchange).
Returned order number, which can be used to query order information and cancel orders.
javascript
function main(){
var id = exchange.Sell(100, 1)
Log("id:", id)
}
python
def main():
id = exchange.Sell(100, 1)
Log("id:", id)
c++
void main() {
auto id = exchange.Sell(100, 1);
Log("id:", id);
}
-
Futures orders
When placing orders for futures, you must pay attention to whether the trading direction is set correctly. If the trading direction and trading function do not match, an error will be reported. The order amount of cryptocurrency futures platforms is the number of contracts unless otherwise specified.
-
Market orders
Note: platform order placement interface is required to support market orders. (When the order type is a sell order, the order amount parameter is the number of operating coins sold), and cryptocurrency futures place orders by market orders form, and the order amount parameter unit is the number of contracts. A few digital currency exchanges in real trading do not support market order interfaces.
javascript// For example, trading pairs: ETH_BTC, sold out by market order function main() { // Note: Sell by a market order, and sell 0.2 ETH coins exchange.Sell(-1, 0.2) }pythondef main(): exchange.Sell(-1, 0.2)c++void main() { exchange.Sell(-1, 0.2); }
exchange.CancelOrder(Id)
exchange.CancelOrder(orderId), the purpose of this function is to cancel an order with Id. Parameter value: Id is the order number, in string type or numeric type (the specific type depends on the return type when placing an order on each platform); return value: bool type.
Return the operation result; true means that the order cancellation request was sent successfully; false means that the cancel order request fails to send (the return value only indicates whether sending request is successful or not, so it is best to call exchange.GetOrders() to check if the platform cancels the order).
javascript
function main(){
var id = exchange.Sell(99999, 1)
exchange.CancelOrder(id)
}
python
def main():
id = exchange.Sell(99999, 1)
exchange.CancelOrder(id)
c++
void main() {
auto id = exchange.Sell(99999, 1);
exchange.CancelOrder(id);
}
The API function of FMZ, which can generate log output functions, such as Log(...), exchange.Buy(Price, Amount) and exchange.CancelOrder(Id). You can follow the necessary parameters with some additional output parameters, such as exchange.CancelOrder(orders[j].Id, orders[j]). In this way, it is canceling orders[j] order that is accompanied by the output of this order information, namely the Order structure of orders[j].
javascript
function main() {
Log("data1", "data2", "data3", "...")
var data2 = 200
var id = exchange.Sell(100000, 0.1, "Incidental data1", data2, "...")
exchange.CancelOrder(id, "Incidental data1", data2, "...")
LogProfit(100, "Incidental data1", data2, "...")
}
python
def main():
Log("data1", "data2", "data3", "...")
data2 = 200
id = exchange.Sell(100000, 0.1, "Incidental data1", data2, "...")
exchange.CancelOrder(id, "Incidental data1", data2, "...")
LogProfit(100, "Incidental data1", data2, "...")
c++
void main() {
Log("data1", "data2", "data3", "...");
int data2 = 200;
auto id = exchange.Sell(100000, 0.1, "Incidental data1", data2, "...");
exchange.CancelOrder(id, "Incidental data1", data2, "...");
LogProfit(100, "Incidental data1", data2, "...");
}
exchange.GetOrder(Id)
exchange.GetOrder(orderId) gets the order details according to the order number. Parameter value: Id is the order number to be obtained, and Id is of string or numeric type (the specific type depends on the return type of each exchange). Return value: Order structure.
(Not supported by some exchanges)
OrderstructureAvgPriceindicates average executed price (some exchanges do not support this field; set it to 0 if they do not support).
javascript
function main(){
var id = exchange.Sell(1000, 1)
// The parameter id is the order number, you need to fill in the number of the order you want to query
var order = exchange.GetOrder(id)
Log("Id:", order.Id, "Price:", order.Price, "Amount:", order.Amount, "DealAmount:",
order.DealAmount, "Status:", order.Status, "Type:", order.Type)
}
python
def main():
id = exchange.Sell(1000, 1)
order = exchange.GetOrder(id)
Log("Id:", order["Id"], "Price:", order["Price"], "Amount:", order["Amount"], "DealAmount:",
order["DealAmount"], "Status:", order["Status"], "Type:", order["Type"])
c++
void main() {
auto id = exchange.Sell(1000, 1);
auto order = exchange.GetOrder(id);
Log("Id:", order.Id, "Price:", order.Price, "Amount:", order.Amount, "DealAmount:",
order.DealAmount, "Status:", order.Status, "Type:", order.Type);
}
exchange.GetOrders()
exchange.GetOrders() gets all unfinished orders. Return value: Order structure array.
For Order structure, please refer to exchange.GetOrder() function description. When the account represented by the exchange object exchange has no pending orders, call exchange.GetOrders() to return an empty array, namely: [].
javascript
function main(){
exchange.Sell(1000, 1)
exchange.Sell(1000, 1)
var orders = exchange.GetOrders()
Log("Information for unfinished order 1, ID:", orders[0].Id, "Price:", orders[0].Price, "Amount:", orders[0].Amount,
"DealAmount:", orders[0].DealAmount, "type:", orders[0].Type)
Log("Information for unfinished order 2, ID:", orders[1].Id, "Price:", orders[1].Price, "Amount:", orders[1].Amount,
"DealAmount:", orders[1].DealAmount, "type:", orders[1].Type)
}
python
def main():
exchange.Sell(1000, 1)
exchange.Sell(1000, 1)
orders = exchange.GetOrders()
Log("Information for unfinished order 1, ID:", orders[0]["Id"], "Price:", orders[0]["Price"], "Amount:", orders[0]["Amount"],
"DealAmount:", orders[0]["DealAmount"], "type:", orders[0]["Type"])
Log("Information for unfinished order 2, ID:", orders[1]["Id"], "Price:", orders[1]["Price"], "Amount:", orders[1]["Amount"],
"DealAmount:", orders[1]["DealAmount"], "type:", orders[1]["Type"])
c++
void main() {
exchange.Sell(1000, 1);
exchange.Sell(1000, 1);
auto orders = exchange.GetOrders();
Log("Information for unfinished order 1, ID:", orders[0].Id, "Price:", orders[0].Price, "Amount:", orders[0].Amount,
"DealAmount:", orders[0].DealAmount, "type:", orders[0].Type);
Log("Information for unfinished order 2, ID:", orders[1].Id, "Price:", orders[1].Price, "Amount:", orders[1].Amount,
"DealAmount:", orders[1].DealAmount, "type:", orders[1].Type);
}
The exchange.GetOrders() function obtains the unfinished order information of the currently set trading pair. It should be noted that the cryptocurrency futures have differences between not only trading pairs but also contract codes.
javascript
// Test OKX contract tradings, to know whether "GetOrders" gets all unfinished contract orders
function main(){
// The next weekly buy order; the price of the order minus 50 guarantees no execution; pending orders
exchange.SetContractType("this_week")
exchange.SetDirection("buy")
var ticker = exchange.GetTicker()
Log(ticker)
exchange.Buy(ticker.Last - 50, 1)
// The next quarterly sell order; the price plus 50 guarantees that it will not be executed, and the pending order has been switched to a quarterly contract
exchange.SetContractType("quarter")
exchange.SetDirection("sell")
ticker = exchange.GetTicker()
Log(ticker)
exchange.Sell(ticker.Last + 50, 1)
// Get the unfinished orders
Log("orders", exchange.GetOrders())
}
python
def main():
exchange.SetContractType("this_week")
exchange.SetDirection("buy")
ticker = exchange.GetTicker()
Log(ticker)
exchange.Buy(ticker["Last"] - 50, 1)
exchange.SetContractType("quarter")
exchange.SetDirection("sell")
ticker = exchange.GetTicker()
Log(ticker)
exchange.Sell(ticker["Last"] + 50, 1)
Log("orders", exchange.GetOrders())
c++
void main() {
exchange.SetContractType("this_week");
exchange.SetDirection("buy");
auto ticker = exchange.GetTicker();
Log(ticker);
exchange.Buy(ticker.Last - 50, 1);
exchange.SetContractType("quarter");
exchange.SetDirection("sell");
ticker = exchange.GetTicker();
Log(ticker);
exchange.Sell(ticker.Last + 50, 1);
Log("orders", exchange.GetOrders());
}
The obtained unfinished order information:
[{"Id":17116430886,"Amount":1,"Price":808.4,"DealAmount":0,"AvgPrice":0,"Status":0,"Type":1,"ContractType":"quarter"}]
It can be seen that in cryptocurrency trading, the orders obtained by exchange.GetOrders() are only the unfinished orders of the currently set contract.
exchange.SetPrecision(...)
exchange.SetPrecision(PricePrecision, AmountPrecision) sets the decimal precision of the price and the symbol order amount; it will be truncated automatically after setting. Parameter value: PricePrecision is of number type, used to control the number of decimal places in price data; AmountPrecision is of number type, used to control the decimal point after the order amount. Both PricePrecision and AmountPrecision must be integer numbers. The backtest does not support the function, and the backtest numerical precision will be processed automatically.
javascript
function main(){
// Set the decimal precision of the price to 2 digits, and set the precision of the quantity of the symbol order to 3 digits
exchange.SetPrecision(2, 3)
}
python
def main():
exchange.SetPrecision(2, 3)
c++
void main() {
exchange.SetPrecision(2, 3);
}
exchange.SetRate(Rate)
exchange.SetRate(Rate) sets the exchange rate of the circulation currency in the exchange. Parameter value: Rate is of number type. Return value: number type.
javascript
function main(){
Log(exchange.GetTicker())
// Set the exchange rate conversion
exchange.SetRate(7)
Log(exchange.GetTicker())
// Set to 1, without conversion
exchange.SetRate(1)
}
python
def main():
Log(exchange.GetTicker())
exchange.SetRate(7)
Log(exchange.GetTicker())
exchange.SetRate(1)
c++
void main() {
Log(exchange.GetTicker());
exchange.SetRate(7);
Log(exchange.GetTicker());
exchange.SetRate(1);
}
Note:
-
If you have set an exchange rate value by using
exchange.SetRate(Rate), such as 7, then, all price information, including the current market price, depth, and order price, of the circulation currency represented by the currentexchangeobjects, will be multiplied by the set exchange rate of 7 for conversion. -
For example,
exchangeis an exchange rate denominated in US dollars. Afterexchange.SetRate(7)is called, all prices of the real trading will be multiplied by 7 and converted to prices close to CNY.
exchange.IO(...)
exchange.IO("api", httpMethod, resource, params, raw), call other functional interfaces of the exchange. Parameter value: httpMehod is of string type; it fills in the request type, such as POST or GET. resource is of string type, it fills in the request path. params is of string type, it fills in the request parameters. raw is the original JSON string parameter and it can be omitted. exchange.IO("api", httpMethod, resource, params, raw) function call will access the exchange interface. When an error occurs and the call fails, it returns a null value (the function with the network request, such as GetTicker() and GetAccount(), etc., return null values when calls fail). Only live trading supports calling the exchange.IO("api", ...) function.
For the example of OKX batch order, use the parameter raw to pass order parameters:
javascript
function main() {
var arrOrders = [
{"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"},
{"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"2","posSide":"long"}
]
// Call exchange.IO to directly access the platform batch ordering interface
var ret = exchange.IO("api", "POST", "/api/v5/trade/batch-orders", "", JSON.stringify(arrOrders))
Log(ret)
}
python
import json
def main():
arrOrders = [
{"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"},
{"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"2","posSide":"long"}
]
ret = exchange.IO("api", "POST", "/api/v5/trade/batch-orders", "", json.dumps(arrOrders))
Log(ret)
c++
void main() {
json arrOrders = R"([
{"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"},
{"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"2","posSide":"long"}
])"_json;
auto ret = exchange.IO("api", "POST", "/api/v5/trade/batch-orders", "", arrOrders.dump());
Log(ret);
}
To use this function, you need to go to the exchange to understand the API interface of the exchange to extend the functions that FMZ has not added (you do not have to worry about the process of parameter encryption, signature and verification when you submit a POST request. FMZ has processed completely at the bottom layer, so you only need to fill in the corresponding parameters).
Note:
If the key value in the params parameter (that is, the Http request parameter) is a string, it needs to be wrapped with single quotes (symbol') on both sides of the parameter value.
For example: bitfinex exchange.
javascript
var amount = 1
var price = 10
var basecurrency = "ltc"
function main () {
// Notice that amount.toString() and price.toString() both have a ' character on the left and right
var message = "symbol=" + basecurrency + "&amount='" + amount.toString() + "'&price='" + price.toString() + "'&side=buy" + "&type=limit"
var id = exchange.IO("api", "POST", "/v1/order/new", message)
}
python
amount = 1
price = 10
basecurrency = "ltc"
def main():
message = "symbol=" + basecurrency + "&amount='" + str(amount) + "'&price='" + str(price) + "'&side=buy" + "&type=limit"
id = exchange.IO("api", "POST", "/v1/order/new", message)
c++
void main() {
auto amount = 1.0;
auto price = 10.0;
auto basecurrency = "ltc";
string message = format("symbol=%s&amount=\"%.1f\"&price=\"%.1f\"&side=buy&type=limit", basecurrency, amount, price);
auto id = exchange.IO("api", "POST", "/v1/order/new", message);
}
Example of accessing OKX interface:
javascript
function main(){
var ret = exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT")
Log(ret)
}
python
def main():
ret = exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT")
Log(ret)
c++
void main() {
auto ret = exchange.IO("api", "GET", "/api/v5/trade/orders-pending", "instType=SPOT");
Log(ret);
}
Returned data during testing:
{"code":"0","data":[],"msg":""}
Other settings of the exchange.IO function:
-
Switch the trading pairs of the current exchange
exchange.IO("currency", "ETH_BTC")javascriptfunction main() { // For example, set the current trading pair of the exchange object on the bot to BTC_USDT, and print the current trading pair market Log(exchange.GetTicker()) // Switch trading pair to LTC_BTC exchange.IO("currency", "LTC_BTC") Log(exchange.GetTicker()) }pythondef main(): Log(exchange.GetTicker()) exchange.IO("currency", "LTC_BTC") Log(exchange.GetTicker())c++void main() { Log(exchange.GetTicker()); exchange.IO("currency", "LTC_BTC"); Log(exchange.GetTicker()); }In this way, the trading pairs configured when the bot is added or the backtest is run will be switched through the codes.
Note:
-
- The backtest system now supports switching trading pairs (only the spot exchange objects of cryptocurrency). During backtest, it should be noted that only trading pairs of the same quote currency can be switched. For example, the current trading pair is
ETH_BTC, which can only be switched toLTC_BTC, notLTC_USDT.
- The backtest system now supports switching trading pairs (only the spot exchange objects of cryptocurrency). During backtest, it should be noted that only trading pairs of the same quote currency can be switched. For example, the current trading pair is
-
- If the
websocketprotocol mode is switched on the Huobi spot exchange objects, you cannot useexchange.IO("currency", "XXX_YYY")to switch currencies.
- If the
-
- For cryptocurrency futures exchanges, if the trading pairs are switched, you need to set up the contract again to determine which contract you want to trade with.
-
- You can also use the new
exchange.SetCurrency(Symbol)function to switch trading pairs, and useexchange.IO("currency","XXX_YYY")method to keep compatibility.
- You can also use the new
-
-
exchange.IOfunction switch the exchange API base address (RESET contract; some exchanges do not support that).
Now the use ofexchange.SetBase(Base)function has been supported to switch the exchange API base address, and useexchange.IO("base","https://xxx.xxx.xxx")method to keep compatibility.For example: When the exchange object is encapsulated, the default base address is
https://api.huobipro.com, to switch to:https://api.huobi.pro, use the following code.javascriptfunction main () { // exchanges[0] is the first exchange object added when the bot is added exchanges[0].IO("base", "https://api.huobi.pro") }pythondef main(): exchanges[0].IO("base", "https://api.huobi.pro")c++void main() { exchanges[0].IO("base", "https://api.huobi.pro"); }Switch the base address back to:
https://api.huobipro.com.javascriptfunction main () { exchanges[0].IO("base", "https://api.huobipro.com") }pythondef main(): exchanges[0].IO("base", "https://api.huobipro.com")c++void main() { exchanges[0].IO("base", "https://api.huobipro.com"); }The ZB exchange is special. There are two addresses: one is the market data interface address, and the other is the trading interface address. Switch the address:
javascriptfunction main() { // The second parameter of exchange.IO is the trading interface address, and the third parameter is the market interface address, which is switched to an address that can be accessed domestically exchange.IO("base", "https://trade.zb.plus", "http://api.zb.plus") // Print the account information Log(exchange.GetAccount()) }pythondef main(): exchange.IO("base", "https://trade.zb.plus", "http://api.zb.plus") Log(exchange.GetAccount())c++void main() { exchange.IO("base", "https://trade.zb.plus", "http://api.zb.plus"); Log(exchange.GetAccount()); } -
Switch market mode
Currently supported exchanges:Exchange Remarks Huobi Spot After supporting WebSocket protocol, the code exchange.IO ("currency", "ETH") cannot be used to switch currencies -
exchange.IO("websocket")Switch the market communication protocol to
websocket(default is rest), the way to get the market quotes will change after switching. Theexchange.GetTicker()andexchange.GetDepth()will be switched towebsocketprotocol to update, from the raw active acquisition of market data to passive acquisition of market data, only Huobi Exchange is supported currently.
When the market mode is the push mechanism, you can call the following function to set:
-
exchange.IO("mode", 0)
Immediate return mode. If the current market data push has not been received from the exchange, the old market data will be returned immediately. If there is new data, the new data will be returned. -
exchange.IO("mode", 1)
Cache mode (default mode). If the latest market data of the exchange has not been received (compared with the data obtained from the previous interface), wait for receiving and then return. If the latest market data has been received before calling this function, the latest data will be returned immediately. -
exchange.IO("mode", 2)
Forced update mode. Enter and wait until the next latest push data of the exchange is received and then return.
If you want to get the latest market information at the first time, you can switch to
websocketand do not callSleepfunction, and test the data immediately. Theexchange.GetTicker()andexchange.GetDepth()functions work in cache mode, such as:javascriptfunction main () { exchange.IO("websocket") while (true) { Log(exchange.GetTicker()) } }pythondef main(): exchange.IO("websocket") while True: Log(exchange.GetTicker())c++void main() { exchange.IO("websocket"); while(true) { Log(exchange.GetTicker()); } } -
-
Exchange leverage account mode
Use
exchange.IO("trade_margin")to switch to the leverage account mode, placing orders and accessing account assets will use the exchange's leverage interface.
Useexchange.IO("trade_normal")to switch back to normal account mode.Supported exchanges:
Exchange Special Remarks OKX The trading pairs of the leverage account mode are different from the ordinary ones, and some trading pairs may not be available. Use exchange.IO("cross", true)to switch to leveraged account full position andexchange.IO("cross", false)to switch to isolated position.Huobi The trading pairs of the leverage account mode are different from the ordinary ones, and some trading pairs may not be available. Huobi leverage account is divided into crossed position and isolated position. Use trade_marginto switch to a leverage account isolated position; usetrade_super_marginto switch to a leverage account crossed position; usetrade_normalto switch to a normal currency modeZB The funds can only be transferred in QC. In the leverage trading sector, the funds between different trading pairs are independent, that is, the number of QC coins under the ETH_QC trading pair is not visible in BTC_QC Binance The leverage accounts are divided into crossed position and isolated position. Use trade_marginto switch to isolated position; usetrade_super_marginto switch to crossed position; usetrade_normalto switch to a normal currency modeGateIO The leverage accounts are divided into crossed position and isolated position. Use trade_marginto switch to isolated position; usetrade_super_marginto switch to crossed position; usetrade_normalto switch to a normal currency modeAscendEx Use exchange.IO("trade_margin")to switch to leverage account mode, and useexchange.IO("trade_normal")to switch back to normal account mode.
exchange.Log(...)
exchange.Log(LogType, Price, Amount) does not place an order when called, and it only records trading information to output the exchange log information.
Note:
- This function is a member function of
exchangeobjects, which is different from the global functionLog(). - Parameter value:
LogTypecan takeLOG_TYPE_BUY,LOG_TYPE_SELL,LOG_TYPE_CANCELandPriceas price, andAmountas quantity. WhenLogTypeisLOG_TYPE_CANCEL,Priceparameter is the order ID.
Use exchange.Log(LogType, Price, Amount) to carry out the live trading follow-up testing, simulated order placement, and to assist in recording orders.
The most common usage case is: using the exchange.IO function to access the creating conditional order interface of the exchange to place conditional orders. However, using exchange.IO function will not output the exchange log information in the bot log recording. Thus, you can use exchange.Log(LogType, Price, Amount) to supplement the log output so as to record the placing order information.
javascript
var id = 123
function main() {
// Order type: buy; price: 999; quantity: 0.1
exchange.Log(LOG_TYPE_BUY, 999, 0.1)
// Cancel the order
exchange.Log(LOG_TYPE_CANCEL, id)
}
python
id = 123
def main():
exchange.Log(LOG_TYPE_BUY, 999, 0.1)
exchange.Log(LOG_TYPE_CANCEL, id)
c++
void main() {
auto id = 123;
exchange.Log(LOG_TYPE_BUY, 999, 0.1);
exchange.Log(LOG_TYPE_CANCEL, id);
}
exchange.HMAC(...)
exchange.HMAC(Algo, OutputAlgo, Data, Key) supports the HMAC encryption calculation of md5/sha256/sha512/sha1, and only live trading running is supported.
exchange.HMAC("sha256", "hex", "xxxxx", "{{secretkey}}")
To quote accessKey, use "{{accesskey}}".
To quote secretKey, use "{{secretkey}}"; or you can use plain text "abc-123-xxxx". "{{accessKey}}","{{secretkey}}" is only valid when this function is used.
OutputAlgo supports: "hex", "base64".
Specific example
Push BitMEX position changes (wss protocol)
javascript
function main() {
var APIKEY = "your Access Key(Bitmex API ID)"
var expires = parseInt(Date.now() / 1000) + 10
var signature = exchange.HMAC("sha256", "hex", "GET/realtime" + expires, "{{secretkey}}")
var client = Dial("wss://www.bitmex.com/realtime", 60)
var auth = JSON.stringify({args: [APIKEY, expires, signature], op: "authKeyExpires"})
var pos = 0
client.write(auth)
client.write('{"op": "subscribe", "args": "position"}')
while (true) {
bitmexData = client.read()
if(bitmexData.table == 'position' && pos != parseInt(bitmexData.data[0].currentQty)){
Log('position change', pos, parseInt(bitmexData.data[0].currentQty), '@')
pos = parseInt(bitmexData.data[0].currentQty)
}
}
}
python
import time
def main():
APIKEY = "your Access Key(Bitmex API ID)"
expires = int(time.time() + 10)
signature = exchange.HMAC("sha256", "hex", "GET/realtime" + str(expires), "{{secretkey}}")
client = Dial("wss://www.bitmex.com/realtime", 60)
auth = json.dumps({"args": [APIKEY, expires, signature], "op": "authKeyExpires"})
pos = 0
client.write(auth)
client.write('{"op": "subscribe", "args": "position"}')
while True:
bitmexData = json.loads(client.read())
if "table" in bitmexData and bitmexData["table"] == "position" and len(bitmexData["data"]) != 0 and pos != bitmexData["data"][0]["currentQty"]:
Log("position change", pos, bitmexData["data"][0]["currentQty"], "@")
pos = bitmexData["data"][0]["currentQty"]
c++
void main() {
auto APIKEY = "your Access Key(Bitmex API ID)";
auto expires = Unix() + 10;
auto signature = exchange.HMAC("sha256", "hex", format("GET/realtime%d", expires), "{{secretkey}}");
auto client = Dial("wss://www.bitmex.com/realtime", 60);
json auth = R"({"args": [], "op": "authKeyExpires"})"_json;
auth["args"].push_back(APIKEY);
auth["args"].push_back(expires);
auth["args"].push_back(signature);
auto pos = 0;
client.write(auth.dump());
client.write("{\"op\": \"subscribe\", \"args\": \"position\"}");
while(true) {
auto bitmexData = json::parse(client.read());
if(bitmexData["table"] == "position" && bitmexData["data"][0].find("currentQty") != bitmexData["data"][0].end() && pos != bitmexData["data"][0]["currentQty"]) {
Log("Test");
Log("position change", pos, bitmexData["data"][0]["currentQty"], "@");
pos = bitmexData["data"][0]["currentQty"];
}
}
}
exchange.Go(...)
exchange.Go(Method, Args...) is the multi-threaded asynchronous supported function, which can turn the operation of all supported functions into asynchronous concurrent (which only supports cryptocurrency exchanges). Parameter value: Method, of string type, namely the function name concurrently called.
Note:
- This function only creates multi-threaded execution tasks when runningin live trading. The backtest does not support multi-threaded concurrent execution tasks (the backtest is available, but it is still executed sequentially).
- After the
exchange.Gofunction returns an object, thewaitfunction is called to retrieve the data returned by the thread through the object. The thread will be released automatically after the data acquisition by callingwaitis completed. If specify the timeout parameter ofwait, the thread will not be released even if the timeout occurs; in this way, the thread will be released automatically only by obtaining the thread result (no matter the concurrently accessed interface is called successfully or unsuccessfully). - In simple terms, no matter the applied thread succeeds or fails, the result must be obtained by the
waitfunction, and the thread resource applied by theexchange.Gofunction is released automatically by the docker. - If the return result of the
waitfunction is not obtained in the end, the thread resources will not be released automatically, which will cause the accumulation of applied threads; if the number of threads exceeds 2000, an error will be reported:"too many routine wait, max is 2000".
Supported functions: GetTicker, GetDepth, GetTrades, GetRecords, GetAccount, GetOrders, GetOrder, CancelOrder, Buy, Sell, GetPosition and IO.
javascript
function main(){
// The following four operations are executed concurrently and asynchronously by multiple threads, without time-consuming, but with immediate return.
var a = exchange.Go("GetTicker")
var b = exchange.Go("GetDepth")
var c = exchange.Go("Buy", 1000, 0.1)
var d = exchange.Go("GetRecords", PERIOD_H1)
// Call the "wait" method, to wait for the return of the asynchronously obtained "ticker" result
var ticker = a.wait()
// Return the depth; it is possible to return null, if the acquisition fails
var depth = b.wait()
// Return the order number; limit the timeout in 1 second; the timeout returns "undefined"; this object can continue to call "wait" to wait if the last "wait" is timeout
var orderId = c.wait(1000)
if(typeof(orderId) == "undefined") {
// Timeout, reacquire
orderId = c.wait()
}
var records = d.wait()
}
python
def main():
a = exchange.Go("GetTicker")
b = exchange.Go("GetDepth")
c = exchange.Go("Buy", 1000, 0.1)
d = exchange.Go("GetRecords", PERIOD_H1)
ticker, ok = a.wait()
depth, ok = b.wait()
orderId, ok = c.wait(1000)
if ok == False:
orderId, ok = c.wait()
records, ok = d.wait()
c++
void main() {
auto a = exchange.Go("GetTicker");
auto b = exchange.Go("GetDepth");
auto c = exchange.Go("Buy", 1000, 0.1);
auto d = exchange.Go("GetRecords", PERIOD_H1);
Ticker ticker;
Depth depth;
Records records;
TId orderId;
a.wait(ticker);
b.wait(depth);
if(!c.wait(orderId, 300)) {
c.wait(orderId);
}
d.wait(records);
}
Note:
- To judge
undefined, usetypeof(xx)==="undefined", becausenull == undefinedis available in JavaScript.
javascript
function main() {
var d = exchange.Go("GetRecords", PERIOD_H1)
// Wait for K-line result
var records = d.wait()
// Here we wait for an asynchronous operation that has been waited and ended; it will return null, and record the error
var ret = d.wait()
}
python
def main():
d = exchange.Go("GetRecords", PERIOD_H1)
records, ok = d.wait()
ret, ok = d.wait()
c++
void main() {
auto d = exchange.Go("GetRecords", PERIOD_H1);
Records records;
d.wait(records);
Records ret;
d.wait(ret);
}
The difference between Python and JavaScript is that Python's wait function returns two parameters: the first is the result returned by the asynchronous API; the second indicates whether the asynchronous call is completed.
Python example:
python
def main():
d = exchange.Go("GetRecords", PERIOD_D1)
# "ok" must return "True", unless the strategy is stopped
ret, ok = d.wait()
# If the waiting times out, or "wait" for an ended instance, "ok" returns "False"
ret, ok = d.wait(100)
ret, ok = d.wait(100)
Acquire multiple exchanges quotations concurrently:
javascript
function main() {
while(true) {
var beginTS = new Date().getTime()
var arrRoutine = []
var arrTicker = []
var arrName = []
for(var i = 0; i < exchanges.length; i++) {
arrRoutine.push(exchanges[i].Go("GetTicker"))
arrName.push(exchanges[i].GetName())
}
for(var i = 0; i < arrRoutine.length; i++) {
arrTicker.push(arrRoutine[i].wait())
}
var endTS = new Date().getTime()
var tbl = {
type: "table",
title: "Market Quotes",
cols: ["Index", "Name", "Latest Executed Price"],
rows: []
}
for(var i = 0; i < arrTicker.length; i++) {
tbl.rows.push([i, arrName[i], arrTicker[i].Last])
}
LogStatus(_D(), "The total time to acquire multiple platforms concurrently is:", endTS - beginTS, "millisecond", "\n", "`" + JSON.stringify(tbl) + "`")
Sleep(500)
}
}
python
import time
import json
def main():
while True:
beginTS = time.time()
arrRoutine = []
arrTicker = []
arrName = []
for i in range(len(exchanges)):
arrRoutine.append(exchanges[i].Go("GetTicker"))
arrName.append(exchanges[i].GetName())
for i in range(len(exchanges)):
ticker, ok = arrRoutine[i].wait()
arrTicker.append(ticker)
endTS = time.time()
tbl = {
"type": "table",
"title": "Market Quote",
"cols": ["Index", "Name", "Latest Executed Price"],
"rows": []
}
for i in range(len(arrTicker)):
tbl["rows"].append([i, arrName[i], arrTicker[i]["Last"]])
LogStatus(_D(), "The total time to acquire multiple platforms concurrently is:", endTS - beginTS, "second", "\n", "`" + json.dumps(tbl) + "`")
Sleep(500)
c++
void main() {
while(true) {
int length = exchanges.size();
auto beginTS = UnixNano() / 1000000;
Ticker arrTicker[length] = {};
string arrName[length] = {};
// Note that to add a few exchange objects, here we need to execute the "exchanges[n].Go" function several times. In this example, we need to add four exchange objects, which can be modified in details
auto r0 = exchanges[0].Go("GetTicker");
auto r1 = exchanges[1].Go("GetTicker");
auto r2 = exchanges[2].Go("GetTicker");
auto r3 = exchanges[3].Go("GetTicker");
GoObj *arrRoutine[length] = {&r0, &r1, &r2, &r3};
for(int i = 0; i < length; i++) {
arrName[i] = exchanges[i].GetName();
}
for(int i = 0; i < length; i++) {
Ticker ticker;
arrRoutine[i]->wait(ticker);
arrTicker[i] = ticker;
}
auto endTS = UnixNano() / 1000000;
json tbl = R"({
"type": "table",
"title": "Market Quote",
"cols": ["Index", "Name", "Latest Executed Price"],
"rows": []
})"_json;
for(int i = 0; i < length; i++) {
json arr = R"(["", "", ""])"_json;
arr[0] = format("%d", i);
arr[1] = arrName[i];
arr[2] = format("%f", arrTicker[i].Last);
tbl["rows"].push_back(arr);
}
LogStatus(_D(), "The total time to acquire multiple platforms concurrently is:", format("%d", endTS - beginTS), "millisecond", "\n", "`" + tbl.dump() + "`");
Sleep(500);
}
}
Concurrent calls to the exchange.IO("api", ...) function:
javascript
function main() {
/*
Use the OKX placing order interface to test
POST /api/v5/trade/order
*/
var beginTS = new Date().getTime()
var param = {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"}
var ret1 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", JSON.stringify(param))
var ret2 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", JSON.stringify(param))
var ret3 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", JSON.stringify(param))
var id1 = ret1.wait()
var id2 = ret2.wait()
var id3 = ret3.wait()
var endTS = new Date().getTime()
Log("id1:", id1)
Log("id2:", id2)
Log("id3:", id3)
Log("Time for placing orders concurrently:", endTS - beginTS, "millisecond")
}
python
import time
import json
def main():
beginTS = time.time()
param = {"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"}
ret1 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", json.dumps(param))
ret2 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", json.dumps(param))
ret3 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", json.dumps(param))
id1, ok1 = ret1.wait()
id2, ok2 = ret2.wait()
id3, ok3 = ret3.wait()
endTS = time.time()
Log("id1:", id1)
Log("id2:", id2)
Log("id3:", id3)
Log("Time for placing orders concurrently:", endTS - beginTS, "second")
c++
void main() {
auto beginTS = UnixNano() / 1000000;
json param = R"({"instId":"BTC-USDT-SWAP","tdMode":"cross","side":"buy","ordType":"limit","px":"16000","sz":"1","posSide":"long"})"_json;
auto ret1 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", param.dump());
auto ret2 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", param.dump());
auto ret3 = exchange.Go("IO", "api", "POST", "/api/v5/trade/order", "", param.dump());
json id1 = R"({})"_json;
json id2 = R"({})"_json;
json id3 = R"({})"_json;
ret1.wait(id1);
ret2.wait(id2);
ret3.wait(id3);
auto endTS = UnixNano() / 1000000;
Log("id1:", id1);
Log("id2:", id2);
Log("id3:", id3);
Log("Time for placing orders concurrently:", endTS - beginTS, "millisecond");
}
Account Information
exchange.GetAccount()
exchange.GetAccount() returns the exchange account information. Return value: Account structure.
Accountstructure
javascript
function main(){
var account = exchange.GetAccount()
Log("Account information, Balance:", account.Balance, "FrozenBalance:", account.FrozenBalance, "Stocks:",
account.Stocks, "FrozenStocks:", account.FrozenStocks)
}
python
def main():
account = exchange.GetAccount()
Log("Account information, Balance", account["Balance"], "FrozenBalance:", account["FrozenBalance"], "Stocks:",
account["Stocks"], "FrozenStocks:", account["FrozenStocks"])
c++
void main() {
auto account = exchange.GetAccount();
Log("Account information, Balance", account.Balance, "FrozenBalance:", account.FrozenBalance, "Stocks:",
account.Stocks, "FrozenStocks:", account.FrozenStocks);
}
If the exchange object is set to a cryptocurrency futures exchange and switched to a contract with USDT as margin (refer to the exchange.SetContractType function to know how to switch), the assets take USDT as margin, which is recorded in the attribute of Balance and FrozenBalance.
javascript
function main(){
// Switch the trading pair
exchange.IO("currency", "BTC_USDT")
// Take OKX futures as an example; set the contract as the contract of the week, and the current trading pair is BTC_USDT, so the current contract is BTC USDT-margined contract of this week
exchange.SetContractType("this_week")
// Acquire the data of the current account assets
var account = exchange.GetAccount()
// The available balance of USDT as margin
Log(account.Balance)
// The frozen amount of USDT as margin
Log(account.FrozenBalance)
}
python
def main():
exchange.IO("currency", "BTC_USDT")
exchange.SetContractType("this_week")
account = exchange.GetAccount()
Log(account["Balance"])
Log(account["FrozenBalance"])
c++
void main() {
exchange.IO("currency", "BTC_USDT");
exchange.SetContractType("this_week");
auto account = exchange.GetAccount();
Log(account.Balance);
Log(account.FrozenBalance);
}
exchange.GetName()
exchange.GetName() returns the exchange name. Return value: string type. Generally, it's used to determine the exchange or exchanges[n] objects in strategy code.
javascript
function main() {
Log("Determine the exchange object to be OKX:", exchange.GetName() == "OKEX")
}
python
def main():
Log("Determine the exchange object to be OKX:", exchange.GetName() == "OKEX")
c++
void main() {
Log("Determine the exchange object to be OKX:", exchange.GetName() == "OKEX");
}
For commanding the line version of docker, you can use the -1 command to print a list of exchange names.
exchange.GetLabel()
exchange.GetLabel() returns the custom label of the exchange. Return value: string type.
The exchange or exchanges[n] objects in strategy codes are normally determined by the labels set when configuring the exchange objects.
exchange.GetCurrency()
exchange.GetCurrency() returns the name of the currency pair operated by the exchange, and the cryptocurrency platform returns a string, such as LTC_BTC. Return value: string type.
exchange.SetCurrency(...)
The exchange.SetCurrency(Symbol) function is used to switch the current trading pair of the exchange object. Compatible with the switching method of exchange.IO ("currency","BTC_USDT"). It supports switching trading pairs in the backtesting system, and the name of the pricing currency cannot be changed when switching trading pairs in the backtesting system (for example, BTC_USDT can be switched to LTC_USDT, but it cannot be switched to LTC_BTC). After switching to a trading pair initially set on the non-backtesting page, the number of trading coins is 0 (for example, during the backtest, the initial value of trading pair on the backtest page is BTC_USDT, the number of BTC is 3, and the number of USDT is 10000. If you immediately switch to LTC_USDT, the number of trading coins after switching will be 0, which means the number of LTC in the account is 0, but the shared USDT amount of the switched trading pairs is still 10000).
javascript
function main() {
var ticker = exchange.GetTicker()
Log(ticker)
Log(exchange.GetAccount())
// Switch trading pairs, and pay attention to changes in market quote data and account information after switching
Log("Switch LTC_USDT: ", exchange.SetCurrency("LTC_USDT"))
ticker = exchange.GetTicker()
Log(ticker)
Log(exchange.GetAccount())
}
python
def main():
ticker = exchange.GetTicker()
Log(ticker)
Log(exchange.GetAccount())
Log(" Switch LTC_USDT: ", exchange.SetCurrency("LTC_USDT"))
ticker = exchange.GetTicker()
Log(ticker)
Log(exchange.GetAccount())
c++
void main() {
auto ticker = exchange.GetTicker();
Log(ticker);
Log(exchange.GetAccount());
exchange.SetCurrency("LTC_USDT");
Log(" Switch LTC_USDT: ");
ticker = exchange.GetTicker();
Log(ticker);
Log(exchange.GetAccount());
}
Backtest operation result:
exchange.GetQuoteCurrency()
exchange.GetQuoteCurrency() returns the name of the base currency operated by the echange. For example, BTC_CNY returns CNY, and ETH_BTC returns BTC. Return value: string type.
Futures Trading
For the cryptocurrency futures exchange objects, the contract code must be specified before calling its market, order and other interfaces, and the exchange.SetContractType function needs to be used to set the current contract code. When switching the current trading pair of the exchange object, you need to call the exchange.SetContractType function to reset the contract code. For digital currency exchange contract codes supported by the platform, please refer to the exchange.SetContractType function.
exchange.GetPosition()
exchange.GetPosition() obtains the current position information. Return value: position structure array. If there is no position, it returns an empty array, namely [].
Positionstructure
Normally, the cryptocurrency futures contracts are divided into two types:
-
Delivery Contract
When a contract is set to a delivery contract, call theexchange.GetPosition()function, and all positions in the delivery contract under the current trading pair will be returned. -
Perpetual Contract
When a contract is set to a perpetual contract, call theexchange.GetPosition()function, and all positions in the perpetual contract under the current trading pair will be returned.
javascript
/*
Note: if there is no position, it will returns an empty array, so you should judge whether the data returned by the interface is a null array, before you use the returned data
For example:
When the exchange is set to OKX futures, if the contract is set to be a delivery contract, when the position data of the current week, the next week, and the quarter is obtained, the data type will be an array of position structure.
When the exchange is set to OKX futures, if the contract is set to a perpetual contract, the array of position structure containing the position data of the perpetual contract will be obtained.
*/
function main(){
exchange.SetContractType("this_week")
exchange.SetMarginLevel(10)
exchange.SetDirection("buy")
exchange.Buy(10000, 2)
var position = exchange.GetPosition()
if(position.length > 0){
Log("Amount:", position[0].Amount, "FrozenAmount:", position[0].FrozenAmount, "Price:",
position[0].Price, "Profit:", position[0].Profit, "Type:", position[0].Type,
"ContractType:", position[0].ContractType)
}
}
python
def main():
exchange.SetContractType("this_week")
exchange.SetMarginLevel(10)
exchange.SetDirection("buy")
exchange.Buy(10000, 2)
position = exchange.GetPosition()
if len(position) > 0:
Log("Amount:", position[0]["Amount"], "FrozenAmount:", position[0]["FrozenAmount"], "Price:",
position[0]["Price"], "Profit:", position[0]["Profit"], "Type:", position[0]["Type"],
"ContractType:", position[0]["ContractType"])
c++
void main() {
exchange.SetContractType("this_week");
exchange.SetMarginLevel(10);
exchange.SetDirection("buy");
exchange.Buy(10000, 2);
auto position = exchange.GetPosition();
if(position.size() > 0) {
Log("Amount:", position[0].Amount, "FrozenAmount:", position[0].FrozenAmount, "Price:",
position[0].Price, "Profit:", position[0].Profit, "Type:", position[0].Type,
"ContractType:", position[0].ContractType);
}
}
exchange.SetMarginLevel(...)
exchange.SetMarginLevel(MarginLevel) sets the leverage size. Parameter value: numeric type.
Set the size of the leverage for placing cryptocurrency futures orders, for example:
javascript
function main() {
exchange.SetMarginLevel(10)
}
python
def main():
exchange.SetMarginLevel(10)
c++
void main() {
exchange.SetMarginLevel(10);
}
For cryptocurrency futures, the leverage mechanisms of cryptocurrency futures exchanges are not uniform. On some exchanges, the leverage value of futures is a parameter in the placing order interface. In this case, calling the SetMarginLevel function cannot generate a network request, but only sets the leverage variable in the bottom layer (used to pass parameters in the placing order interface). The futures leverage of some exchanges is a setting of the exchange, which needs to be set on the exchange website page or by using the API interface. At this point, calling the SetMarginLevel function will generate a network request, and it may fail to set the leverage for various reasons. For example: if there are current positions and pending orders, the leverage value may not be set for this trading pair and the underlying object.
Notes for setting the leverage in the strategy design:
- Only cryptocurrency futures are supported.
- The backtest supports switching leverages.
exchange.SetDirection(...)
exchange.SetDirection(Direction) set exchange.Buy or exchange.Sell to make directions for placing futures orders. Parameter value: string type.
The SetDirection function sets the correspondence between the futures trading direction and the order placing function:
| Order Placing Function | Parameter Setting Direction for "SetDirection" Function | Remarks |
|---|---|---|
| exchange.Buy | "buy" | buy open long position |
| exchange.Buy | "closesell" | buy close short position |
| exchange.Sell | "sell" | sell open short position |
| exchange.Sell | "closebuy" | sell close long position |
The parameter Direction can take the four parameters, including buy, closebuy, sell and closesell.
javascript
function main(){
// Make an example for setting OKX futures weekly contract
exchange.SetContractType("this_week")
// Set 5 times of leverage
exchange.SetMarginLevel(5)
// Set the order placing type into placing long order
exchange.SetDirection("buy")
// Place an order with the contract quantity of 2, at the price of 10,000
exchange.Buy(10000, 2)
exchange.SetMarginLevel(5)
exchange.SetDirection("closebuy")
exchange.Sell(1000, 2)
}
python
def main():
exchange.SetContractType("this_week")
exchange.SetMarginLevel(5)
exchange.SetDirection("buy")
exchange.Buy(10000, 2)
exchange.SetMarginLevel(5)
exchange.SetDirection("closebuy")
exchange.Sell(1000, 2)
c++
void main() {
exchange.SetContractType("this_week");
exchange.SetMarginLevel(5);
exchange.SetDirection("buy");
exchange.Buy(10000, 2);
exchange.SetMarginLevel(5);
exchange.SetDirection("closebuy");
exchange.Sell(1000, 2);
}
exchange.SetContractType(...)
exchange.SetContractType(ContractType) set the contract type. Parameter value: string type. In the cryptocurrency strategies, take "BTC_USDT" as an example. When switch trading pairs by exchange.SetCurrency("BTC_USDT") or exchange.IO("currency", "BTC_USDT"), you need to use the exchange.SetContractType function to reset the contract to determine the current contract that needs to be operated under the new trading pair. The system determines whether it is a currency-based contract or a U-based contract based on the trading pair. For example, when the trading pair is set to BTC_ USDT, use function exchange.SetContractType to set the contract code swap, which is set as the USDT-based perpetual contract of BTC. If the trading pair is BTC_ USD, use function exchange.SetContractType to set the contract code swap, which is set as the currency-based perpetual contract of BTC.
Unless otherwise specified, the delivery contract code in a cryptocurrency futures contract generally includes:
this_week: weekly contractnext_week: next week contractquarter: quarterly contractnext_quarter: next quarter contract
Unless otherwise specified, the perpetual contract code in a cryptocurrency futures contract generally includes:
swap: perpetual contract
Set the current contract to a weekly contract:
javascript
function main() {
// Set to weekly contract
exchange.SetContractType("this_week")
}
python
def main():
exchange.SetContractType("this_week")
c++
void main() {
exchange.SetContractType("this_week");
}
Detailed description on the contract naming of each supported cryptocurrency exchange is shown as follows:
-
OKX
Set to perpetual contract:exchange.SetContractType("swap")
Set to this week contract:exchange.SetContractType("this_week")
Set to next week contract:exchange.SetContractType("next_week")
Set to quarterly contract:exchange.SetContractType("quarter")
Set to next quarter contract:exchange.SetContractType("next_quarter")
OKX USDT-margined contract can be switched to the contract using USDT settlement corresponding to the current contract by switchingtrading pair(or set it directly when adding the exchange objects). For example:javascriptfunction main() { // The default trading pair is BTC_USD, the contract is set to the weekly contract, and the contract is the crypto-margined contract exchange.SetContractType("this_week") Log("ticker:", exchange.GetTicker()) // Switch trading pairs, and then set a contract to a USDT-margined contract, which is different from the crypto-margined contract exchange.IO("currency", "BTC_USDT") exchange.SetContractType("swap") Log("ticker:", exchange.GetTicker()) }pythondef main(): exchange.SetContractType("this_week") Log("ticker:", exchange.GetTicker()) exchange.IO("currency", "BTC_USDT") exchange.SetContractType("swap") Log("ticker:", exchange.GetTicker())c++void main() { exchange.SetContractType("this_week"); Log("ticker:", exchange.GetTicker()); exchange.IO("currency", "BTC_USDT"); exchange.SetContractType("swap"); Log("ticker:", exchange.GetTicker()); } -
Futures_HuobiDM
Set to this week contract:exchange.SetContractType("this_week")
Set to next week contract:exchange.SetContractType("next_week")
Set to quarterly contract:exchange.SetContractType("quarter")
Set to next quarter contract:exchange.SetContractType("next_quarter")
Set to perpetual contract:exchange.SetContractType("swap")The platform supports the USDT-margined contracts. Take the BTC contract as an example. You can switch to a USDT-margined contract only by using
exchange.SetCurrency("BTC_USDT"), or by setting the current trading pair toBTC_USDTdirectly when you configure the bot parameters and add exchange objects. After switching the trading pair, you need to callexchange.SetContractTypefunction again to set the contract. -
Futures_BitMEX
Set to perpetual contract:exchange.SetContractType("XBTUSD"),exchange.SetContractType("APTUSDT").
The contract is settled at some specific time, and log in to the official website of BitMEX to check out each contract code for more details. For example:exchange.SetContractType("XBTM19"). -
Futures_GateIO
Set to this week contract:exchange.SetContractType("this_week").
Set to next week contract:exchange.SetContractType("next_week").
Set to quarterly contract:exchange.SetContractType("quarter").
Set to next quarter contract:exchange.SetContractType("next_quarter").
Set to perpetual contract:exchange.SetContractType("swap").
The exchange supports the USDT-margined contracts. Take the BTC contract as an example. You can switch to a USDT-margined contract only by usingexchange.SetCurrency("BTC_USDT"), or by setting the current trading pair toBTC_USDTdirectly when you configure the live trading parameters and add exchange objects. After switching the trading pair, you need to callexchange.SetContractTypefunction again to set the contract. -
Futures_Deribit
Set to perpetual contract:exchange.SetContractType("BTC-PERPETUAL")
Supporting Deribit USDC contract; callexchange.SetContractType("ADA_USDC-PERPETUAL")to set ADA USDC-margined perpetual contract.
The contract is settled at some specific time, and log in to the official website of Deribit to check out each contract code for more details, such as:exchange.SetContractType("BTC-27APR18"). -
Futures_KuCoin
- Crypto-Margined Contract;
For example, set the trading pair toBTC_USD, and then set the contract code, which is called crypto-margined contract.
Set to perpetual contract:exchange.SetContractType("swap")
Set to quarterly contract:exchange.SetContractType("quarter")
Set to next quarter contract:exchange.SetContractType("next_quarter") - USDT-Margined Contract:
For example, set the trading pair toBTC_USDT, and then set the contract code, which is USDT-margined contract.
Set to perpetual contract:exchange.SetContractType("swap").
- Crypto-Margined Contract;
-
Futures_Binance
Binance Futures defaults to no setting contract status, so you need to set the contract first.
Set to perpetual contract:exchange.SetContractType("swap")
Binance Futures perpetual contracts can be USDT-margined contracts. For example, in theBTCUSDT-margined contract, the trading pair is set toBTC_USDT. Binance Futures perpetual contracts also include crypto-margined contracts. For example, in the crypto-margined contract, set the trading pair toBTC_USD.Set to quarterly contract:
exchange.SetContractType("quarter")
The delivery contracts include crypto-margined contracts (namely taking the used currency as margin). For example, in the quarterly contract ofBTC, the trading pair is set toBTC_USD, and then set the contract codeexchange.SetContractType("quarter"), which is setting BTC crypto-margined quarterly contract.Set to next quarter contract:
exchange.SetContractType("next_quarter")
For example, in the quarterly crypto-margined contract ofBTC, the trading pair is set toBTC_USD, and then set the contract codeexchange.SetContractType("next_quarter). Binance supports part of USDT-margined delivery contracts, such as setting the BTC trading pair toBTC_USDT, and then set the contract code. -
Futures_Bibox
Bibox perpetual contract code:swap.
Set to perpetual contract:exchange.SetContractType("swap"). -
Futures_AOFEX
AOFEX perpetual contract code:swap.
Set to perpetual contract:exchange.SetContractType("swap"). -
Futures_BFX
BFX perpetual contract code:swap.
Set to perpetual contract:exchange.SetContractType("swap"). -
Futures_Bybit
Bybit defaults to the perpetual contract of the current trading pair. Contract code:swap.
Quarterly contract code:quarter.
Next quarter contract code:next_quarter. -
Futures_Kraken
Kraken defaults to no setting contract status, so you need to set the contract code. Contract code:
swap: perpetual contract.
month: monthly contract.
quarter: quarterly contract.
next_quarter: next quarter contract. -
Futures_Bitfinex
Bitfinex defaults to the perpetual contract of the current trading pair. Contract code:swap. -
Futures_Bitget
Bitget defaults to the perpetual contract of the current trading pair. Contract code:swap. The trading pair is set toBTC_USD, indicating crypto-margined contract; the trading pair is set toBTC_USDT, indicating USDT-margined contract. The simulated contracts can set the trading pairs into:SBTC_USDandBTC_SUSDT. -
Futures_dYdX
The contract code of dYdX perpetual contract:swap.
Set to perpetual contract:exchange.SetContractType("swap"). dYdX only has USDT-margined contracts. -
Futures_MEXC
The contract code of MEXC perpetual contract:swap.
Set to perpetual contract:exchange.SetContractType("swap"). The trading pair is set toBTC_USD, indicating crypto-margined contract; the trading pair is set toBTC_USDT, indicating USDT-margined contract.
When writing cryptocurrency strategies, call exchange.SetContractType(ContractType) function and the detailed contract information set by the contract parameter ContractType will be returned.
For example, running the strategy code of OKX contract set as the exchange object:
javascript
function main(){
// Set to weekly contract
var ret = exchange.SetContractType("this_week")
// Return the information of the weekly contract
Log(ret)
}
python
def main():
ret = exchange.SetContractType("this_week")
Log(ret)
c++
void main() {
auto ret = exchange.SetContractType("this_week");
Log(ret);
}
Run the strategy in JavaScript language, and print ret data, namely the detailed information of this_week contract:
javascript
{
"instrument":"BTC-USD-191101",
"InstrumentID":"BTC-USD-191101"
}
exchange.GetContractType()
exchange.GetContractType() returns the currently set contract code of the exchange object (exchange), return value: string.
javascript
function main () {
Log(exchange.SetContractType("this_week"))
Log(exchange.GetContractType())
}
python
def main():
Log(exchange.SetContractType("this_week"))
Log(exchange.GetContractType())
c++
void main() {
Log(exchange.SetContractType("this_week"));
Log(exchange.GetContractType());
}
Futures_OP Errors
The following table describes the error information related to the exchange objects of the cryptocurrency futures contracts:
| Value | Function display error | Trigger function | Description |
|---|---|---|---|
| 0 | Futures_OP 0 | exchange.SetMarginLevel | error of calling leverage function |
| 1 | Futures_OP 1 | exchange.SetDirection | error of setting futures trading direction function |
| 2 | Futures_OP 2 | exchange.SetContractType | error of setting contract function |
| 3 | Futures_OP 3 | exchange.GetPosition | error of getting position function |
| 4 | Futures_OP 4 | exchange.IO | error of calling IO function |
Options Trading
Cryptocurrency Options
Use exchange.SetContractType function to set contracts; Different exchanges have different options contract codes.
Cryptocurrency options exchanges supported by FMZ Quant Trading platform
-
Deribit
For Deribit exchange, it is only necessary to call theexchange.SetContractTypefunction to set the contract as an options contract. After setting up the options contract, when calling the market interface such asGetTicker, all the market data of the options contract is obtained. Useexchange.Sellandexchange.Buyfunctions to place an order, and pay attention to the trading direction when placing an order, and set the trading direction byexchange.SetDirection. Use theexchange.Cancelfunction to cancel an order; use theexchange.GetPositionfunction to query positions.The strategy code for reference: The Testing Strategy of Deribit Options
-
OKX
Setting up contracts, placing orders, canceling orders, querying orders, and obtaining market quotes, etc. are of the same operation asDeribit, and the contract code format isBTC-USD-200626-4500-C. You can query the information related to the contract through the interfacehttps://www.okx.com/api/v5/public/instruments.For example, to query the information of BTC options contracts:
javascriptfunction main() { Log(HttpQuery("https://www.okx.com/api/v5/public/instruments?instType=OPTION&uly=BTC-USD")) }pythonimport json import urllib.request def main(): ret = json.loads(urllib.request.urlopen("https://www.okx.com/api/v5/public/instruments?instType=OPTION&uly=BTC-USD").read().decode('utf-8')) Log(ret)c++void main() { Log(HttpQuery("https://www.okx.com/api/v5/public/instruments?instType=OPTION&uly=BTC-USD")); } -
Futures_HuobiDM
For example, Huobi options contract code:BTC-USDT-201225-P-13000; the contract is aBTCcontract; the exercise date is December 25, 2020; the options are Put Options (PUT); the strike price is $13,000.
For Call Options, the premium paid by the buyer is USDT, indicating the USDT in the account assets is used; the seller's margin is currency, which is guaranteed by the currency in the assets.
For Put Options, the premium paid by the buyer is USDT, indicating the USDT in the account assets is used; the seller's margin is USDT, which is guaranteed by the USDT in the assets. -
Futures_Bybit
It supports the USDC option of the Bybit exchange, and set the trading pair toETH_USDC, call functionexchange.SetContractTypeto set the contract as an option contract. Example of option contract code:ETH-25NOV22-1375-P.
Network Settings
exchange.SetBase(Base)
The exchange.SetBase(Base) function is used to switch the API base address recorded in the exchange objects; For example, switch to OKX domain name https://aws.okx.com, compatible with exchange.IO("base","https://aws.okx.com") switching method. The backtest system does not support switching the API base address of the exchange (the backtest system is a sandbox environment, not a real interface to access the exchange).
javascript
function main() {
// Use the default base address
Log(exchange.GetTicker())
// Switch to https://aws.okx.com
exchange.SetBase("https://aws.okx.com")
Log(exchange.GetTicker())
}
python
def main():
Log(exchange.GetTicker())
exchange.SetBase("https://aws.okx.com")
Log(exchange.GetTicker())
c++
void main() {
Log(exchange.GetTicker());
exchange.SetBase("https://aws.okx.com");
Log(exchange.GetTicker());
}
exchange.SetProxy(...)
exchange.SetProxy(...) switches to proxy server to access the exchange. This function has no return value (obtained by variables, and what you obtain is undefined). If the proxy setting fails, a null value will be returned when the interface is called, only for the rest protocol. Each exchange object exchanges[n] can set a proxy. After setting up the proxy, the exchange interface will be accessed through the proxy.
Take the first added exchange object exchange, namely: exchanges[0], as an example:
- Set the proxy, without username and password:
exchange.SetProxy("socks5://127.0.0.1:8889") - Set the proxy, and enter the username and password:
exchange.SetProxy("socks5://username:[email protected]:8889")(usernameis user's name,passwordis the password.) - Switch to the normal mode, without proxy:
exchange.SetProxy("")
Supporting to set the IP address requested by the exchange object.
- Global specification
The docker ofwindowssystem interface version can set directly, which is shown as the following image:
Other dockers operated by the command prompt use parameter -I to specify IP addresses:
-
Based on exchange to specify:
function main(){ exchange.SetProxy("ip://10.0.3.15") exchange.GetTicker() // The requested 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
Millisecondis millisecond, and 1,000 milliseconds equals 1 second. - Only need to set once.
- Only for the rest protocol.
SetTimeoutis 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 returnsnullif the call fails (due to the exchange server problems or network problems, etc.).javascriptfunction 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 attributeValid.c++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.
c++
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.
javascript
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.
javascript
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:
javascript
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.
javascript
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.
javascript
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.
javascript
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.
javascript
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.
javascript
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.
javascript
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.
c++
// 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
javascriptfunction 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
javascriptfunction 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
javascriptfunction 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
javascriptfunction 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:
javascript
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.
javascript
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:
javascript
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:
-
- 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 thetypesparameter:tuple(a uint256, b uint8, c address). - Variables corresponding to type
-
- Variables corresponding to type
bytes:
"0011" - Variables corresponding to type
Support for sequential encoding of arrays or types containing arrays:
javascript
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:
javascript
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.
javascript
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:
javascript
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
decimalsmethod is aconstantmethod ofERC20that does not generategasconsumption, and it can query the precision data of a particulartoken. Thedecimalsmethod has no parameters. Return value: the precision data oftoken.javascriptfunction 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
allowancemethod is aconstantmethod ofERC20that does not generategasconsumption, and it can query the authorized amount of a certaintokenfor a certain contract address. Theallowancemethod 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 oftoken.javascriptfunction 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 beUniswap V3 router v1. -
approve
The
approvemethod is a non-constantmethod ofERC20that generatesgasconsumption, which is used to authorize atokenoperation amount to a certain contract address. Theapprovemethod 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.javascriptfunction 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 beUniswap V3 router v1address.
0xde0b6b3a7640000: The number of authorizations, represented here using a hexadecimal string, corresponds to a decimal value of1e18, divided by thetokenprecision unit in the example (i.e., 1e18), yielding 1tokenauthorized.The third parameter of the
exchange.IOfunction is passed the method nameapprove, which can also be written in the form ofmethodId, for example: "0x571ac8b0". It is also possible to write the full standard method name, such as ''approve(address,uint256)''. -
multicall
The
multicallmethod is a non-constant method ofUniswap V3, which will generategasconsumption and be used to exchange tokens in multiple ways. Themulticallmethod 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
multicallmethod calls, please refer to the public "Uniswap V3 Trading Class Library" template of our platform.Pseudocode is used here to describe some details:
javascriptexchange.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 thetokenIntoken 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/noncesetting for method calls, we use pseudocode to describe again:javascriptexchange.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 theexchange.IOfunction. You can omit thenonceand use the system defaults, or do not setgasLimit/gasPrice/nonceand use all the system default values.It should be noted that in the example, the attribute of
stateMutabilityinmulticall(uint256,bytes[])method ispayable, and thevalueparameter needs to be passed in. The attribute ofstateMutability":"payable"can be viewed from theABI. Theexchange.IOfunction will determine the required parameters according to thestateMutabilityattribute in theABIthat has been registered. If thestateMutabilityattribute isnonpayable, the parametervaluedoes not need to be passed in.
Common function calls
- Gets the address of the wallet configured by the exchange object.
javascript
function main() {
Log(exchange.IO("address")) // Print the wallet address of the private key configured on the exchange object.
}
- Switch blockchain RPC nodes.
javascript
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:
javascript
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
# 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))
c++
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.
javascript
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])
}
python
def main():
r = exchange.GetRecords(PERIOD_M15)
macd = TA.MACD(r, 12, 26, 9)
Log("DIF:", macd[0], "DEA:", macd[1], "MACD:", macd[2])
c++
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.
javascript
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])
}
python
def main():
r = exchange.GetRecords(PERIOD_M15)
kdj = TA.KDJ(r, 9, 3, 3)
Log("k:", kdj[0], "d:", kdj[1], "j:", kdj[2])
c++
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.
javascript
function main(){
var records = exchange.GetRecords(PERIOD_M30)
var rsi = TA.RSI(records, 14)
Log(rsi)
}
python
def main():
r = exchange.GetRecords(PERIOD_M30)
rsi = TA.RSI(r, 14)
Log(rsi)
c++
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.
javascript
function main(){
var records = exchange.GetRecords(PERIOD_M30)
var atr = TA.ATR(records, 14)
Log(atr)
}
python
def main():
r = exchange.GetRecords(PERIOD_M30)
atr = TA.ATR(r, 14)
Log(atr)
c++
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.
javascript
function main(){
var records = exchange.GetRecords(PERIOD_M30)
var obv = TA.OBV(records)
Log(obv)
}
python
def main():
r = exchange.GetRecords(PERIOD_M30)
obv = TA.OBV(r)
Log(obv)
c++
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.
javascript
function main(){
var records = exchange.GetRecords(PERIOD_M30)
var ma = TA.MA(records, 14)
Log(ma)
}
python
def main():
r = exchange.GetRecords(PERIOD_M30)
ma = TA.MA(r, 14)
Log(ma)
c++
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.
javascript
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)
}
}
python
def main():
r = exchange.GetRecords()
if r and len(r) > 9:
ema = TA.EMA(r, 9)
Log(ema)
c++
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].
javascript
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)
}
}
python
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)
c++
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:
c++
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
-
JavaScriptlibrary
http://mathjs.org/javascriptfunction 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/
javascriptfunction 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))) // true }javascriptfunction main() { var sum = _.reduce([1, 2, 3], function(memo, num){return memo + num}, 0) Log(sum) } -
C++library
https://nlohmann.github.io/json/c++void main() { json table = R"({"type": "table", "title": "Position Information", "cols": ["Column1", "Column2"], "rows": [["abc", "def"], ["ABC", "support color #ff0000"]]})"_json; LogStatus("`" + table.dump() + "`"); LogStatus("Fist line message\n`" + table.dump() + "`\nThird line message"); json arr = R"([])"_json; arr.push_back(table); arr.push_back(table); LogStatus("`" + arr.dump() + "`"); table = R"({ "type" : "table", "title" : "Position Operation", "cols" : ["Column1", "Column2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}] ] })"_json; LogStatus("`" + table.dump() + "`", "\n`" + R"({"type": "button", "cmd": "coverAll", "name": "close position"})"_json.dump() + "`"); }
Function Indicators in Talib Library
Among the parameters of the following functions, Records[Close] represents the close price in the incoming k-line data, Array() represents an array, and Array(outInteger) represents returning an array of integer data.
Example code for calling CCI indicator:
javascript
function main() {
var records = exchange.GetRecords()
var cci = talib.CCI(records, 14)
Log(cci)
}
python
import talib
def main():
records = exchange.GetRecords()
# the parameter "14" can be default
cci = talib.CCI(records.High, records.Low, records.Close, 14)
Log(cci)
c++
void main() {
auto records = exchange.GetRecords();
auto cci = talib.CCI(records, 14);
Log(cci);
}
-
Pattern Recognition:Indicator Description CDL2CROWS Two Crows CDL2CROWS(Records[Open,High,Low,Close]) = Array(outInteger) CDL3BLACKCROWS Three Black Crows CDL3BLACKCROWS(Records[Open,High,Low,Close]) = Array(outInteger) CDL3INSIDE Three Inside Up/Down CDL3INSIDE(Records[Open,High,Low,Close]) = Array(outInteger) CDL3LINESTRIKE Three-Line Strike CDL3LINESTRIKE(Records[Open,High,Low,Close]) = Array(outInteger) CDL3OUTSIDE Three Outside Up/Down CDL3OUTSIDE(Records[Open,High,Low,Close]) = Array(outInteger) CDL3STARSINSOUTH Three Stars In The South CDL3STARSINSOUTH(Records[Open,High,Low,Close]) = Array(outInteger) CDL3WHITESOLDIERS Three Advancing White Soldiers CDL3WHITESOLDIERS(Records[Open,High,Low,Close]) = Array(outInteger) CDLABANDONEDBABY Abandoned Baby CDLABANDONEDBABY(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger) CDLADVANCEBLOCK Advance Block CDLADVANCEBLOCK(Records[Open,High,Low,Close]) = Array(outInteger) CDLBELTHOLD Belt-hold CDLBELTHOLD(Records[Open,High,Low,Close]) = Array(outInteger) CDLBREAKAWAY Breakaway CDLBREAKAWAY(Records[Open,High,Low,Close]) = Array(outInteger) CDLCLOSINGMARUBOZU Closing Marubozu CDLCLOSINGMARUBOZU(Records[Open,High,Low,Close]) = Array(outInteger) CDLCONCEALBABYSWALL Concealing Baby Swallow CDLCONCEALBABYSWALL(Records[Open,High,Low,Close]) = Array(outInteger) CDLCOUNTERATTACK Counterattack CDLCOUNTERATTACK(Records[Open,High,Low,Close]) = Array(outInteger) CDLDARKCLOUDCOVER Dark Cloud Cover CDLDARKCLOUDCOVER(Records[Open,High,Low,Close],Penetration = 0.5) = Array(outInteger) CDLDOJI Doji CDLDOJI(Records[Open,High,Low,Close]) = Array(outInteger) CDLDOJISTAR Doji Star CDLDOJISTAR(Records[Open,High,Low,Close]) = Array(outInteger) CDLDRAGONFLYDOJI Dragonfly Doji CDLDRAGONFLYDOJI(Records[Open,High,Low,Close]) = Array(outInteger) CDLENGULFING Engulfing Pattern CDLENGULFING(Records[Open,High,Low,Close]) = Array(outInteger) CDLEVENINGDOJISTAR Evening Doji Star CDLEVENINGDOJISTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger) CDLEVENINGSTAR Evening Star CDLEVENINGSTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger) CDLGAPSIDESIDEWHITE Up/Down-gap side-by-side white lines CDLGAPSIDESIDEWHITE(Records[Open,High,Low,Close]) = Array(outInteger) CDLGRAVESTONEDOJI Gravestone Doji CDLGRAVESTONEDOJI(Records[Open,High,Low,Close]) = Array(outInteger) CDLHAMMER Hammer CDLHAMMER(Records[Open,High,Low,Close]) = Array(outInteger) CDLHANGINGMAN Hanging Man CDLHANGINGMAN(Records[Open,High,Low,Close]) = Array(outInteger) CDLHARAMI Harami Pattern CDLHARAMI(Records[Open,High,Low,Close]) = Array(outInteger) CDLHARAMICROSS Harami Cross Pattern CDLHARAMICROSS(Records[Open,High,Low,Close]) = Array(outInteger) CDLHIGHWAVE High-Wave Candle CDLHIGHWAVE(Records[Open,High,Low,Close]) = Array(outInteger) CDLHIKKAKE Hikkake Pattern CDLHIKKAKE(Records[Open,High,Low,Close]) = Array(outInteger) CDLHIKKAKEMOD Modified Hikkake Pattern CDLHIKKAKEMOD(Records[Open,High,Low,Close]) = Array(outInteger) CDLHOMINGPIGEON Homing Pigeon CDLHOMINGPIGEON(Records[Open,High,Low,Close]) = Array(outInteger) CDLIDENTICAL3CROWS Identical Three Crows CDLIDENTICAL3CROWS(Records[Open,High,Low,Close]) = Array(outInteger) CDLINNECK In-Neck Pattern CDLINNECK(Records[Open,High,Low,Close]) = Array(outInteger) CDLINVERTEDHAMMER Inverted Hammer CDLINVERTEDHAMMER(Records[Open,High,Low,Close]) = Array(outInteger) CDLKICKING Kicking CDLKICKING(Records[Open,High,Low,Close]) = Array(outInteger) CDLKICKINGBYLENGTH Kicking - bull/bear determined by the longer marubozu CDLKICKINGBYLENGTH(Records[Open,High,Low,Close]) = Array(outInteger) CDLLADDERBOTTOM Ladder Bottom CDLLADDERBOTTOM(Records[Open,High,Low,Close]) = Array(outInteger) CDLLONGLEGGEDDOJI Long Legged Dojz CDLLONGLEGGEDDOJI(Records[Open,High,Low,Close]) = Array(outInteger) CDLLONGLINE Long Line Candle CDLLONGLINE(Records[Open,High,Low,Close]) = Array(outInteger) CDLMARUBOZU Marubozu CDLMARUBOZU(Records[Open,High,Low,Close]) = Array(outInteger) CDLMATCHINGLOW Matching Low CDLMATCHINGLOW(Records[Open,High,Low,Close]) = Array(outInteger) CDLMATHOLD Mat Hold CDLMATHOLD(Records[Open,High,Low,Close],Penetration = 0.5) = Array(outInteger) CDLMORNINGDOJISTAR Morning Doji Star CDLMORNINGDOJISTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger) CDLMORNINGSTAR Morning Star CDLMORNINGSTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger) CDLONNECK On-Neck Pattern CDLONNECK(Records[Open,High,Low,Close]) = Array(outInteger) CDLPIERCING Piercing Pattern CDLPIERCING(Records[Open,High,Low,Close]) = Array(outInteger) CDLRICKSHAWMAN Rickshaw Man CDLRICKSHAWMAN(Records[Open,High,Low,Close]) = Array(outInteger) CDLRISEFALL3METHODS Rising/Falling Three Methods CDLRISEFALL3METHODS(Records[Open,High,Low,Close]) = Array(outInteger) CDLSEPARATINGLINES Separating Lines CDLSEPARATINGLINES(Records[Open,High,Low,Close]) = Array(outInteger) CDLSHOOTINGSTAR Shooting Star CDLSHOOTINGSTAR(Records[Open,High,Low,Close]) = Array(outInteger) CDLSHORTLINE Short Line Candle CDLSHORTLINE(Records[Open,High,Low,Close]) = Array(outInteger) CDLSPINNINGTOP Spinning Top CDLSPINNINGTOP(Records[Open,High,Low,Close]) = Array(outInteger) CDLSTALLEDPATTERN Stalled Pattern CDLSTALLEDPATTERN(Records[Open,High,Low,Close]) = Array(outInteger) CDLSTICKSANDWICH Stick Sandwich CDLSTICKSANDWICH(Records[Open,High,Low,Close]) = Array(outInteger) CDLTAKURI Takuri (Dragonfly Doji with very long lower shadow) CDLTAKURI(Records[Open,High,Low,Close]) = Array(outInteger) CDLTASUKIGAP Tasuki Gap CDLTASUKIGAP(Records[Open,High,Low,Close]) = Array(outInteger) CDLTHRUSTING Thrusting Pattern CDLTHRUSTING(Records[Open,High,Low,Close]) = Array(outInteger) CDLTRISTAR Tristar Pattern CDLTRISTAR(Records[Open,High,Low,Close]) = Array(outInteger) CDLUNIQUE3RIVER Unique 3 River CDLUNIQUE3RIVER(Records[Open,High,Low,Close]) = Array(outInteger) CDLUPSIDEGAP2CROWS Upside Gap Two Crows CDLUPSIDEGAP2CROWS(Records[Open,High,Low,Close]) = Array(outInteger) CDLXSIDEGAP3METHODS Upside/Downside Gap Three Methods CDLXSIDEGAP3METHODS(Records[Open,High,Low,Close]) = Array(outInteger) -
Volume Indicators:Indicator Description AD Chaikin A/D Line AD(Records[High,Low,Close,Volume]) = Array(outReal) ADOSC Chaikin A/D Oscillator ADOSC(Records[High,Low,Close,Volume],Fast Period = 3,Slow Period = 10) = Array(outReal) OBV On Balance Volume OBV(Records[Close],Records[Volume]) = Array(outReal) -
Math Transform:Indicator Description ACOS Vector Trigonometric ACos ACOS(Records[Close]) = Array(outReal) ASIN Vector Trigonometric ASin ASIN(Records[Close]) = Array(outReal) ATAN Vector Trigonometric ATan ATAN(Records[Close]) = Array(outReal) CEIL Vector Ceil CEIL(Records[Close]) = Array(outReal) COS Vector Trigonometric Cos COS(Records[Close]) = Array(outReal) COSH Vector Trigonometric Cosh COSH(Records[Close]) = Array(outReal) EXP Vector Arithmetic Exp EXP(Records[Close]) = Array(outReal) FLOOR Vector Floor FLOOR(Records[Close]) = Array(outReal) LN Vector Log Natural LN(Records[Close]) = Array(outReal) LOG10 Vector Log10 LOG10(Records[Close]) = Array(outReal) SIN Vector Trigonometric Sin SIN(Records[Close]) = Array(outReal) SINH Vector Trigonometric Sinh SINH(Records[Close]) = Array(outReal) SQRT Vector Square Root SQRT(Records[Close]) = Array(outReal) TAN Vector Trigonometric Tan TAN(Records[Close]) = Array(outReal) TANH Vector Trigonometric Tanh TANH(Records[Close]) = Array(outReal) -
Math Operators:Indicator Description MAX Highest value over a specified period MAX(Records[Close],Time Period = 30) = Array(outReal) MAXINDEX Index of highest value over a specified period MAXINDEX(Records[Close],Time Period = 30) = Array(outInteger) MIN Lowest value over a specified period MIN(Records[Close],Time Period = 30) = Array(outReal) MININDEX Index of lowest value over a specified period MININDEX(Records[Close],Time Period = 30) = Array(outInteger) MINMAX Lowest and highest values over a specified period MINMAX(Records[Close],Time Period = 30) = [Array(outMin),Array(outMax)] MINMAXINDEX Indexes of lowest and highest values over a specified period MINMAXINDEX(Records[Close],Time Period = 30) = [Array(outMinIdx),Array(outMaxIdx)] SUM Summation SUM(Records[Close],Time Period = 30) = Array(outReal) -
Cycle Indicators:Indicator Description HT_DCPERIOD Hilbert Transform - Dominant Cycle Period HT_DCPERIOD(Records[Close]) = Array(outReal) HT_DCPHASE Hilbert Transform - Dominant Cycle Phase HT_DCPHASE(Records[Close]) = Array(outReal) HT_PHASOR Hilbert Transform - Phasor Components HT_PHASOR(Records[Close]) = [Array(outInPhase),Array(outQuadrature)] HT_SINE Hilbert Transform - SineWave HT_SINE(Records[Close]) = [Array(outSine),Array(outLeadSine)] HT_TRENDMODE Hilbert Transform - Trend vs Cycle Mode HT_TRENDMODE(Records[Close]) = Array(outInteger) -
Volatility Indicators:Indicator Description ATR Average True Range ATR(Records[High,Low,Close],Time Period = 14) = Array(outReal) NATR Normalized Average True Range NATR(Records[High,Low,Close],Time Period = 14) = Array(outReal) TRANGE True Range TRANGE(Records[High,Low,Close]) = Array(outReal) -
Overlap Studies:Indicator Description BBANDS Bollinger Bands BBANDS(Records[Close],Time Period = 5,Deviations up = 2,Deviations down = 2,MA Type = 0) = [Array(outRealUpperBand),Array(outRealMiddleBand),Array(outRealLowerBand)] DEMA Double Exponential Moving Average DEMA(Records[Close],Time Period = 30) = Array(outReal) EMA Exponential Moving Average EMA(Records[Close],Time Period = 30) = Array(outReal) HT_TRENDLINE Hilbert Transform - Instantaneous Trendline HT_TRENDLINE(Records[Close]) = Array(outReal) KAMA Kaufman Adaptive Moving Average KAMA(Records[Close],Time Period = 30) = Array(outReal) MA Moving Average MA(Records[Close],Time Period = 30,MA Type = 0) = Array(outReal) MAMA MESA Adaptive Moving Average MAMA(Records[Close],Fast Limit = 0.5,Slow Limit = 0.05) = [Array(outMAMA),Array(outFAMA)] MIDPOINT Midpoint over period MIDPOINT(Records[Close],Time Period = 14) = Array(outReal) MIDPRICE Midpoint Price over period MIDPRICE(Records[High,Low],Time Period = 14) = Array(outReal) SAR Parabolic SAR SAR(Records[High,Low],Acceleration Factor = 0.02,AF Maximum = 0.2) = Array(outReal) SAREXT Parabolic SAR - Extended SAREXT(Records[High,Low],Start Value = 0,Offset on Reverse = 0,AF Init Long = 0.02,AF Long = 0.02,AF Max Long = 0.2,AF Init Short = 0.02,AF Short = 0.02,AF Max Short = 0.2) = Array(outReal) SMA Simple Moving Average SMA(Records[Close],Time Period = 30) = Array(outReal) T3 Triple Exponential Moving Average (T3) T3(Records[Close],Time Period = 5,Volume Factor = 0.7) = Array(outReal) TEMA Triple Exponential Moving Average TEMA(Records[Close],Time Period = 30) = Array(outReal) TRIMA Triangular Moving Average TRIMA(Records[Close],Time Period = 30) = Array(outReal) WMA Weighted Moving Average WMA(Records[Close],Time Period = 30) = Array(outReal) -
Statistic Functions:Indicator Description LINEARREG Linear Regression LINEARREG(Records[Close],Time Period = 14) = Array(outReal) LINEARREG_ANGLE Linear Regression Angle LINEARREG_ANGLE(Records[Close],Time Period = 14) = Array(outReal) LINEARREG_INTERCEPT Linear Regression Intercept LINEARREG_INTERCEPT(Records[Close],Time Period = 14) = Array(outReal) LINEARREG_SLOPE Linear Regression Slope LINEARREG_SLOPE(Records[Close],Time Period = 14) = Array(outReal) STDDEV Standard Deviation STDDEV(Records[Close],Time Period = 5,Deviations = 1) = Array(outReal) TSF Time Series Forecast TSF(Records[Close],Time Period = 14) = Array(outReal) VAR Variance VAR(Records[Close],Time Period = 5,Deviations = 1) = Array(outReal) -
Momentum Indicators:Indicator Description ADX Average Directional Movement Index ADX(Records[High,Low,Close],Time Period = 14) = Array(outReal) ADXR Average Directional Movement Index Rating ADXR(Records[High,Low,Close],Time Period = 14) = Array(outReal) APO Absolute Price Oscillator APO(Records[Close],Fast Period = 12,Slow Period = 26,MA Type = 0) = Array(outReal) AROON Aroon AROON(Records[High,Low],Time Period = 14) = [Array(outAroonDown),Array(outAroonUp)] AROONOSC Aroon Oscillator AROONOSC(Records[High,Low],Time Period = 14) = Array(outReal) BOP Balance Of Power BOP(Records[Open,High,Low,Close]) = Array(outReal) CCI Commodity Channel Index CCI(Records[High,Low,Close],Time Period = 14) = Array(outReal) CMO Chande Momentum Oscillator CMO(Records[Close],Time Period = 14) = Array(outReal) DX Directional Movement Index DX(Records[High,Low,Close],Time Period = 14) = Array(outReal) MACD Moving Average Convergence/Divergence MACD(Records[Close],Fast Period = 12,Slow Period = 26,Signal Period = 9) = [Array(outMACD),Array(outMACDSignal),Array(outMACDHist)] MACDEXT MACD with controllable MA type MACDEXT(Records[Close],Fast Period = 12,Fast MA = 0,Slow Period = 26,Slow MA = 0,Signal Period = 9,Signal MA = 0) = [Array(outMACD),Array(outMACDSignal),Array(outMACDHist)] MACDFIX Moving Average Convergence/Divergence Fix 12/26 MACDFIX(Records[Close],Signal Period = 9) = [Array(outMACD),Array(outMACDSignal),Array(outMACDHist)] MFI Money Flow Index MFI(Records[High,Low,Close,Volume],Time Period = 14) = Array(outReal) MINUS_DI Minus Directional Indicator MINUS_DI(Records[High,Low,Close],Time Period = 14) = Array(outReal) MINUS_DM Minus Directional Movement MINUS_DM(Records[High,Low],Time Period = 14) = Array(outReal) MOM Momentum MOM(Records[Close],Time Period = 10) = Array(outReal) PLUS_DI Plus Directional Indicator PLUS_DI(Records[High,Low,Close],Time Period = 14) = Array(outReal) PLUS_DM Plus Directional Movement PLUS_DM(Records[High,Low],Time Period = 14) = Array(outReal) PPO Percentage Price Oscillator PPO(Records[Close],Fast Period = 12,Slow Period = 26,MA Type = 0) = Array(outReal) ROC Rate of change : ((price/prevPrice)-1)*100 ROC(Records[Close],Time Period = 10) = Array(outReal) ROCP Rate of change Percentage: (price-prevPrice)/prevPrice ROCP(Records[Close],Time Period = 10) = Array(outReal) ROCR Rate of change ratio: (price/prevPrice) ROCR(Records[Close],Time Period = 10) = Array(outReal) ROCR100 Rate of change ratio 100 scale: (price/prevPrice)*100 ROCR100(Records[Close],Time Period = 10) = Array(outReal) RSI Relative Strength Index RSI(Records[Close],Time Period = 14) = Array(outReal) STOCH Stochastic STOCH(Records[High,Low,Close],Fast-K Period = 5,Slow-K Period = 3,Slow-K MA = 0,Slow-D Period = 3,Slow-D MA = 0) = [Array(outSlowK),Array(outSlowD)] STOCHF Stochastic Fast STOCHF(Records[High,Low,Close],Fast-K Period = 5,Fast-D Period = 3,Fast-D MA = 0) = [Array(outFastK),Array(outFastD)] STOCHRSI Stochastic Relative Strength Index STOCHRSI(Records[Close],Time Period = 14,Fast-K Period = 5,Fast-D Period = 3,Fast-D MA = 0) = [Array(outFastK),Array(outFastD)] TRIX 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA TRIX(Records[Close],Time Period = 30) = Array(outReal) ULTOSC Ultimate Oscillator ULTOSC(Records[High,Low,Close],First Period = 7,Second Period = 14,Third Period = 28) = Array(outReal) WILLR Williams' %R WILLR(Records[High,Low,Close],Time Period = 14) = Array(outReal) -
Price Transform:Indicator Description AVGPRICE Average Price AVGPRICE(Records[Open,High,Low,Close]) = Array(outReal) MEDPRICE Median Price MEDPRICE(Records[High,Low]) = Array(outReal) TYPPRICE Typical Price TYPPRICE(Records[High,Low,Close]) = Array(outReal) WCLPRICE Weighted Close Price WCLPRICE(Records[High,Low,Close]) = Array(outReal)
Fundamental Data
FMZ Quant Trading platform supports backtesting and querying various fundamental data in the bot. Fundamental data are provided in real time by FMZ Quant Trading platform data center, and the data center will continue to collect and summarize various fundamental data.
Data Invocation
Use exchange.GetData(Source) function to obtain the fundamental data.
The fundamental data of cryptocurrency have not been collected temporarily.
Data Format
The fundamental data of cryptocurrency have not been collected temporarily.
API Extension of FMZ Platform
FMZ Quant Trading platform supports the programmatic invocation of various functions of the Platform, and it has opened up the extended API.
It supports the API authority management extension, as shown in the figure:
It allows the newly added API KEY to have all permissions. You can enter * in the API permission input box to open all interface permissions. Specify specific interface permissions, and enter the corresponding extended API function names, separated by commas, such as GetRobotDetail, DeleteRobot, that is, give this API KEY the permission to obtain the interface of bot detailed information and delete bot interface.
Create ApiKey
API Return Code
| Description | Code |
|---|---|
| Successful execution | 0 |
| Wrong API Key | 1 |
| Wrong signature | 2 |
| Nonce error | 3 |
| Incorrect method | 4 |
| Incorrect parameter | 5 |
| Internal unknown error | 6 |
Bot Status
(Normal start)
| Status | Code |
|---|---|
| Idle | 0 |
| In operation | 1 |
| Stopping | 2 |
| Signed out | 3 |
| Stopped | 4 |
| The strategy has errors | 5 |
(abnormal)
| Status | Code |
|---|---|
| The strategy has expired, and please contact the writer to buy it again | -1 |
| No docker found | -2 |
| Strategy compilation error | -3 |
| The bot is already running | -4 |
| Insufficient balance | -5 |
| The number of concurrent strategies exceeds the limit | -6 |
Ways of Verification
Token Verification
Use md5 encryption method to verify.
Python, Golang language call examples:
python
#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import json
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
try:
import md5
import urllib2
from urllib import urlencode
except:
import hashlib as md5
import urllib.request as urllib2
from urllib.parse import urlencode
accessKey = 'f27bfcXXXXXXXX013c62e98XXXXX817a'
secretKey = 'ffeXXXXXXXX085ff7269XXXXXXXX6f82'
def api(method, *args):
d = {
'version': '1.0',
'access_key': accessKey,
'method': method,
'args': json.dumps(list(args)),
'nonce': int(time.time() * 1000),
}
d['sign'] = md5.md5(('%s|%s|%s|%d|%s' % (d['version'], d['method'], d['args'], d['nonce'], secretKey)).encode('utf-8')).hexdigest()
# Note: for the timeout problem of "urllib2.urlopen" function, you can set the timeout time; for example, urllib2.urlopen ('https://www.fmz.com/api/v1', urlencode(d).encode('utf-8'), timeout = 10), that is, set timeout for 10 seconds
return json.loads(urllib2.urlopen('https://www.fmz.com/api/v1', urlencode(d).encode('utf-8')).read().decode('utf-8'))
# Return the docker list
print(api('GetNodeList'))
# Return the exchange list
print(api('GetPlatformList'))
# GetRobotList (offset, length, robotStatus, label); passing "-1" means obtaining all
print(api('GetRobotList', 0, 5, -1, 'member2'))
# CommandRobot(robotId, cmd) sends commands to the bot
print(api('CommandRobot', 123, 'ok'))
# StopRobot(robotId) returns the bot status code
print(api('StopRobot', 123))
# RestartRobot(robotId) returns the bot status code
print(api('RestartRobot', 123))
# GetRobotDetail(robotId) returns detailed bot information
print(api('GetRobotDetail', 123))
mylang
package main
import (
"fmt"
"time"
"encoding/json"
"crypto/md5"
"encoding/hex"
"net/http"
"io/ioutil"
"strconv"
"net/url"
)
// Fill in your own FMZ platform api key
var apiKey string = ""
// Fill in your own FMZ platform secret key
var secretKey string = ""
var baseApi string = "https://www.fmz.com/api/v1"
func api(method string, args ... interface{}) (ret interface{}) {
// Process args
jsonStr, err := json.Marshal(args)
if err != nil {
panic(err)
}
params := map[string]string{
"version" : "1.0",
"access_key" : apiKey,
"method" : method,
"args" : string(jsonStr),
"nonce" : strconv.FormatInt(time.Now().UnixNano() / 1e6, 10),
}
data := fmt.Sprintf("%s|%s|%s|%v|%s", params["version"], params["method"], params["args"], params["nonce"], secretKey)
h := md5.New()
h.Write([]byte(data))
sign := h.Sum(nil)
params["sign"] = hex.EncodeToString(sign)
// http request
client := &http.Client{}
// request
urlValue := url.Values{}
for k, v := range params {
urlValue.Add(k, v)
}
urlStr := urlValue.Encode()
request, err := http.NewRequest("GET", baseApi + "?" + urlStr, nil)
if err != nil {
panic(err)
}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
ret = string(b)
return
}
func main() {
settings := map[string]interface{}{
"name": "hedge test",
"strategy": 104150,
// K-line period parameter "60" means 60 seconds
"period": 60,
"node" : 73938,
"appid": "member2",
"exchanges": []interface{}{
map[string]interface{}{
"eid": "Exchange",
"label" : "test_bjex",
"pair": "BTC_USDT",
"meta" : map[string]interface{}{
// Fill in the access key
"AccessKey": "",
// Fill in the secret key
"SecretKey": "",
"Front" : "http://127.0.0.1:6666/exchange",
},
},
},
}
method := "RestartRobot"
fmt.Println("Call interface:", method)
ret := api(method, 124577, settings)
fmt.Println("main ret:", ret)
}
Direct Verification
It supports verification without token (pass secret_key directly), you can generate a URL that can be accessed directly. For example, the URL that directly gives interactive instructions to the bot, which can be used for TradingView or the WebHook callback in other cases.
For CommandRobot(RobotId, Cmd) function, the parameter nonce does not need verification, and the access frequency and visit times of the interface are not limited.
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
Under the circumstance that the direct verification is supported, only CommandRobot interface is supported to obtain the Body data in the request. For example, the settings in the WebHook URL of TradingView:
https://www.fmz.com/api/v1?access_key=fd3be82e9e6e6ed4439f2793a5e9ca&secret_key=520b9f10f3768e6ad1af59ff25184&method=CommandRobot&args=[130350,+""]
Pay attention to setting according to the following format: args=[130350,+""], in which 130350is the bot ID of FMZ Quant Trading platform.
Settings in the message box (the requested "Body" data to be sent):
-
JSON format:
{"close": {{close}}, "name": "aaa"}The bot with
IDof186515can receive the interactive instruction:{"close": 39773.75, "name": "aaa"}. -
Text format:
BTCUSDTPERP Crossing 39700.00 close: {{close}}The bot with
IDof186515can receive the interactive instruction:BTCUSDTPERP Crossing 39700.00 close: 39739.4.
Examples of Python & Golang language calls:
python
#!/usr/bin/python
# -*- coding: utf-8 -*-
import json
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
try:
import urllib2
except:
import urllib.request as urllib2
accessKey = 'your accessKey'
secretKey = 'your secretKey'
def api(method, *args):
return json.loads(urllib2.urlopen(('https://www.fmz.com/api/v1?access_key=%s&secret_key=%s&method=%s&args=%s' % (accessKey, secretKey, method, json.dumps(list(args)))).replace(' ', '')).read().decode('utf-8'))
# If APIKEY does not have the interface permission, the call to print(api('RestartRobot', 130350)) will fail, and the returned data is: {'code': 4, 'data': None}
# print(api('RestartRobot', 130350))
# Print bot details with ID 130350
print(api('GetRobotDetail', 130350))
mylang
package main
import (
"fmt"
"encoding/json"
"net/http"
"io/ioutil"
"net/url"
)
// Fill in your own FMZ platform api key
var apiKey string = "your access_key"
// Fill in your own FMZ platform secret key
var secretKey string = "your secret_key"
var baseApi string = "https://www.fmz.com/api/v1"
func api(method string, args ... interface{}) (ret interface{}) {
jsonStr, err := json.Marshal(args)
if err != nil {
panic(err)
}
params := map[string]string{
"access_key" : apiKey,
"secret_key" : secretKey,
"method" : method,
"args" : string(jsonStr),
}
// http request
client := &http.Client{}
// request
urlValue := url.Values{}
for k, v := range params {
urlValue.Add(k, v)
}
urlStr := urlValue.Encode()
request, err := http.NewRequest("GET", baseApi + "?" + urlStr, nil)
if err != nil {
panic(err)
}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
ret = string(b)
return
}
func main() {
method := "GetRobotDetail"
fmt.Println("Call interface:", method)
ret := api(method, 130350)
fmt.Println("main ret:", ret)
}
Use the extended API on FMZ Quant to realize "TradingView" alert signal trading
Explanation of Extended API
-
The extended API of FMZ Quant Trading platform
https://www.fmz.com/api/v1?The above symbol
?is followed by the request parameters.The request parameters described in
Pythonlanguage:python{ 'version' : '1.0', # Access key, applied on the account management page 'access_key': '8a1f6c3785fd78a1848320e0b19js99f', # The specific method called 'method' : 'GetNodeList', # The parameter list of specific method algorithm 'args' : [], # Timestamp, in the unit of millisecond, is allowed to have 1 hour of time error both before and after the standard timestamp;the nonce must be greater than the nonce value of the last access 'nonce' : 1516292399361, # Signature 'sign' : '085b63456c93hfb243a757366600f9c2' }Each parameter name is separated by the character
&, and the parameter names and values are connected with the symbol=. The complete request URL (takingmethod=GetNodeListas an example):https://www.fmz.com/api/v1? access_key=8a1f6c3785fd78a1848320e0b19js99f& nonce=1516292399361& args=%5B%5D& sign=085b63456c93hfb243a757366600f9c2& version=1.0& method=GetNodeListPay attention that there is no
secret_keyamong request parameters. -
Signature Method
In the request parameters, the parametersignis an encrypted string, and the encryption method is as follows.According to the format:
version + "|" + method + "|" + args + "|" + nonce + "|" + secretKeyAfter concatenating strings, use MD5 encryption algorithm to encrypt the string and convert it to a hexadecimal data string value, referred as the value of parameter
sign.The reference
Pythoncode of the signature part:python# parameter d = { 'version': '1.0', 'access_key': accessKey, 'method': method, 'args': json.dumps(list(args)), 'nonce': int(time.time() * 1000), } # Calculate "sign" signature (encrypted) d['sign'] = md5.md5(('%s|%s|%s|%d|%s' % (d['version'], d['method'], d['args'], d['nonce'], secretKey)).encode('utf-8')).hexdigest()
GetNodeList()
GetNodeList() returns the docker list of the FMZ Quant Trading platform account corresponding to the API KEY in the request.
-
Parameter
None -
Return value
{ "code": 0, "data": { "result": { "nodes": [{ // Version number "build": "3.3", "date": "2018-01-19 10:35:24", "id": 34500, "ip": "126.28.21.120", // Number of the bots running on this docker "loaded": 1, "name": "iZ9116xhkgtZ", // Whether online "online": true, // Operation system "os": "linux", // Whether to enable offline alert "wd": 0 }, ... ] }, "error": null } }
GetRobotGroupList()
GetRobotGroupList() returns the bot grouping list of FMZ Quant Trading platform account corresponding to the API KEY in the request.
-
Parameter
None -
Return value
{ "code":0, "data":{ "result":{ // All bot grouping information: group ID, group name "items":[{"id":1122,"name":"test"}] }, "error":null } }
GetPlatformList()
GetPlatformList() returns the list of exchanges that have been added by the FMZ Quant Trading platform account corresponding to the API KEY in the request.
-
Parameter
None -
Return value
{ "code": 0, "data": { "result": { "platforms": [{ "eid": "Huobi", "id": 12483, // The label when adding a platform "label": "huobi - test ETH/ETC", "logo": "huobi.png", "name": "huobi", "stocks": ["LTC_BTC", "ETH_BTC", "ETC_BTC", "BCH_BTC"], "website": "https://www.huobi.pro/" }, { "eid": "AEX", "id": 16345, "label": "AEX", "logo": "", "name": "AEX", "stocks": ["LTC_BTC", "ETH_BTC", "ETC_BTC", "BCC_BTC"], "website": "https://www.aex.com/" }, ... ] }, "error": null } }
GetRobotList(...)
GetRobotList(offset, length, robotStatus, label) returns the robot list of the FMZ Quant Trading platform account corresponding to the API KEY in the request.
-
Parameter
offset,lengthandrobotStatusare of integer type, andlabelis of string type.offsetlengthrobotStatus(passing "-1" means getting all)label(custom label; all bots with the label can be flirted out)
For
Pythoncode example, refer to the above verification method - Token Verification, and the following is thePythoninvocation example:
print api('GetRobotList', 'member2')print the information of all robots with the custom labelmember2.
print api('GetRobotList', 0, 5, -1, 'member2')pages from 0 to 5 and list up to 5 robots labeled withmember2. -
Return value
{ "code": 0, "data": { "result": { "all": 53, "bots": [{ "date": "2017-12-25 09:29:27", "end_time": "2017-12-28 17:44:21", "id": 66054, // If the value is 1, the bot is a virtual platform bot "is_sandbox": 1, "name": "C++ test strategy", "node_guid": "705d9aaaaaaaa93b49baaaaa787581cb087", "profit": 0, "public": 0, "refresh": 151345645647000, "start_time": "2017-12-28 17:44:15", "status": 3, "strategy_id": 65365, "strategy_isowner": true, "strategy_name": "C++ Version Docker API Test Strategy(cryptocurrency futures and spot markets)", "wd": 0 }, ... ] }, "error": null } }
CommandRobot(...)
CommandRobot(RobotId, Cmd) the interface sends the interactive command to the corresponding robot (robot ID: RobotId) of the API KEY in the request of the FMZ Quant account (the interactive command is captured by the GetCommand() API called in the strategy and returns).
-
Parameter
RobotIdis of integer type, namely the botID;Cmdis of string type, which sends interactive commands to bots.RobotId, the botID, can be obtained by theGetRobotList(...)interface.Cmdis the interactive command sent to the bot; the command will be captured by the functionGetCommand(), which triggers the interactive logic in the strategy (to realize the interactive logic in the strategy, please refer to theGetCommand()function in the file).
-
Return value
{ // The API request was successfully executed "code": 0, "data": { // However, sending a command to a bot that is not running returns failure "result": false, "error": null } }
StopRobot(RobotId)
StopRobot(RobotId) stops running the bot with the specified ID (robot ID: RobotId ), corresponding to the API KEY in the request of the FMZ Quant account.
-
Parameter
RobotIdis of integer type, namely the botID, which can be obtained byGetRobotList(...)interface;StopRobot(RobotId)interface returns the status code of the stopped robot. -
Return value
{ "code": 0, "data": { // 2 means stopping "result": 2, "error": null } }
RestartRobot(...)
RestartRobot(RobotId, Settings) restarts the robot with the specified ID (robot ID: RobotId), corresponding to the API KEY in the request of the FMZ Quant account.
If the robot is created by the extended API, the extended API RestartRobot (RobotId, Settings) must be used to restart, and the Settings parameter must be passed.
The robot created on the platform page can be restarted through the extended API or by clicking the button on the page. You can pass or don't pass the Settings parameter, or just pass the the RobotId parameter. If you only pass the RobotId parameter, start the bot according to the current bot settings.
-
Parameter
-
Without configuring the bot, the strategy parameter
Settings:
RobotIdis of integer type; the robotIDcan be obtained byGetRobotList(...). -
With configuring the bot, the strategy parameter
Settings:
RobotIdis of integer type; the botIDcan be obtained byGetRobotList(...).
Settingsis ofJSONobject type.Settingsparameter format as follows:Settings = { "name": "hedge test", // Strategy parameter "args": [["Interval", 500]], // Strategy ID, which can be obtained with "GetStrategyList" method "strategy": 25189, // K-line period parameter "60" means 60 seconds "period": 60, // Specify on which docker to run; if the attribute is not written, it will be automatically assigned to run "node" : 51924, // Custom field "appid": "member2", "exchanges": [ // ZB; "pid" can be obtained by "GetPlatformList" method {"pid": 15445, "pair": "ETH_BTC"}, // OKEX; 2 exchange objects are configured {"pid": 13802, "pair": "BCH_BTC"}, // In addition to the platforms ("pid" identification) configured by the FMZ dashboard, you can also set exchange configuration information that has not been configured to operate the bot {"eid": "OKEX", "pair": "ETH_BTC", "meta" :{"AccessKey": "xxx", "SecretKey": "yyy"}}, {"eid": "Huobi", "pair": "BCH_BTC", "meta" :{"AccessKey": "xxx", "SecretKey": "yyy"}} ] }Note:
When you use the sensitive information, such as platformAPI KEY, including"meta":{"AccessKey":"xxx","SecretKey":"yyy"}in the configuration ofeid, you should know that FMZ does not store the data. The data will be sent directly to the docker program, so this information must be configured every time the bot is created or restarted.To restart the bot that uses the plugin to support the exchange, when configuring the
Settingsparameter, you should make the following settings for theexchangesattribute:{"eid": "Exchange", "label" : "testXXX", "pair": "ETH_BTC", "meta" :{"AccessKey": "123", "SecretKey": "1234", "Front" : "http://127.0.0.1:6666/XXX"}}labelattribute is to set a label for the exchange object accessed by the current general protocol, which can be obtained by theexchange.GetLabel()function in the strategy.
-
-
Return value
{ "code": 0, "data": { // 1 means running "result": 1, "error": null } }
GetRobotDetail(RobotId)
GetRobotDetail(RobotId) obtains the detailed information about the robot with the specified ID (robot ID: RobotId), corresponding to the API KEY in the request of the FMZ Quant Trading platform account.
-
Parameter
RobotIdis of integer type; the robotIDcan be obtained byGetRobotList(...). -
Return value
{ "code": 0, "data": { "result": { "robot": { // Next payment time, namely the effective cut-off time after the current payment "charge_time": 1561992608, // Elapsed Time "charged": 3600, // Amount consumed (0.125 CNY = 12500000 / 1e8) "consumed": 12500000, "date": "2019-07-01 21:50:08", "debug": "{\"Nano\":1561989722431145193,\"Stderr\":\"\",\"Stdout\":\"\"}", // Stop time "end_time": "2019-07-01 22:02:02", // The docker ID assigned when the robot is running; if it is automatic, the value is -1 "fixed_id": 85960, "id": 150288, "is_deleted": 0, // Whether it has the permission to manage the bot "is_manager": true, // Whether it is virtual exchange "is_sandbox": 0, // Robot name "name": "Spread monitoring2", // Docker ID "node_id": 85960, // The exchange objects configured by the robot "pexchanges": { // 14703 is eid, and "GateIO" is exchange name "14703": "GateIO", "15445": "ZB", "42960": "OKEX", "44314": "Huobi" }, // label information of the exchange object configured by the robot "plabels": { "14703": "Gate.IO (old name: BTER)", "15445": "ZB", "42960": "OKEX spot V3 test", "44314": "Huobi - newest test" }, "profit": 0, // Whether to show public "public": 0, // Recent active time "refresh": 1561989724000, "robot_args": "[[\"TickInterval\",500],[\"StrOnePair\",\"spot:Huobi:spot;spot:OKEX:spot;false;60;5;0;0\"],[\"StrTwoPair\",\"spot:ZB:spot;spot:GateIO:spot;false;60;5;0;0\"],[\"StrThreePair\",\"null\"],[\"StrFourPair\",\"null\"],[\"StrSixPair\",\"null\"],[\"StrFivePair\",\"null\"],[\"ResetChart\",false]]", "start_time": "2019-07-01 22:00:54", // Robot status "status": 4, "strategy_args": "[[\"TickInterval\",\"Detection frequency (ms)\",\"This is millisecond. Don't set it too small.\",500],[\"StrOnePair\",\"Combination1\",\"Spread Combination\",\"spot:Huobi:spot;spot:OKCoin:spot;false;60;5;0;0\"],[\"StrTwoPair\",\"Combination2\",\"Spread Combination\",\"future:Futures_OKCoin:this_week;spot:OKCoin:spot;false;60;5;0;0\"],[\"StrThreePair\",\"Combination3\",\"Spread Combination\",\"future:Futures_OKCoin:this_week;future:Futures_OKCoin:quarter;true;60;5;0;0\"],[\"StrFourPair\",\"Combination4\",\"Spread Combination\",\"null\"],[\"StrSixPair\",\"Combination6\",\"Combination\",\"null\"],[\"StrFivePair\",\"Combination5\",\"Combination\",\"null\"],[\"ResetChart\",\"whether to clear the previous chart\",\"clear the previous chart\",false]]", // Configured exchange objects, set trading pair information "strategy_exchange_pairs": "[60,[44314,42960,15445,14703],[\"BTC_USDT\",\"BTC_USDT\",\"ETH_USDT\",\"ETH_USDT\"]]", // Strategy ID "strategy_id": 21337, // Strategy's last modification time "strategy_last_modified": "2018-11-29 12:07:58", // Strategy name "strategy_name": "Digital currency spread monitoring and analysis", "summary": "Polling time consuming: 500ms\n`[{\"type\":\"table\",\"title\":\"pair basic data\",\"cols\":[\"ID\",\"NameA - NameB\",\"SymbolA - SymbolB\",\"UpdCycle\",\"isUSD\",\"Collect\"],\"rows\":[[\"0 \",\"Huobi/OKEX\",\"spot/spot\",60,false,\"612ms\"],[\"1 \",\"ZB/GateIO\",\"spot/spot\",60,false,\"501ms\"]]},{\"type\":\"table\",\"title\":\"pair market data\",\"cols\":[\"ID\",\"NameA - NameB\",\"SymbolA - SymbolB\",\"A_Bids1\",\"B_Asks1\",\"Plus\",\"A_Asks1\",\"B_Bids1\",\"Minus\"],\"rows\":[[\"0 \",\"Huobi/OKEX\",\"spot/spot\",10518.02,10525.1,-7.08,10520,10523,-3],[\"1 \",\"ZB/GateIO\",\"spot/spot\",285.68,286,-0.32,285.8,285.85,-0.05]]},{\"type\":\"table\",\"title\":\"pair statistical data\",\"cols\":[\"ID\",\"NameA - NameB\",\"SymbolA - SymbolB\",\"Maximum spread\",\"Minimum spread\",\"Mean positive premium\",\"Mean negative premium\"],\"rows\":[[\"0 \",\"Huobi/OKEX\",\"spot/spot\",0,-3,0,-1.47],[\"1 \",\"ZB/GateIO\",\"spot/spot\",0.03,-0.05,0.03,-0.05]]}]`\n", // Whether to enable offline alert "wd": 0 } }, "error": null } }The
summaryattribute in the returned data (information on the bot status bar; cached for 10 second; not the latest data) currently has a data amount limit (cached data); the data amount limit is 200KB, and the excess data will be truncated. If you need more status bar information data, you can use theGetRobotLogsinterface to get (whenGetRobotLogsobtains the information of status bar, the fieldsummaryis the latest data).The attribute description of
strategy_exchange_pairs, take the following data as an example:[60,[44314,42960,15445,14703],[\"BTC_USDT\",\"BTC_USDT\",\"ETH_USDT\",\"ETH_USDT\"]]The first data
60represents the default K-line period set by the bot is 1 minute, namely 60 seconds.[44314,42960,15445,14703]is the exchange objecteidconfigured for the bot (according to the addition order).
[\"BTC_USDT\",\"BTC_USDT\",\"ETH_USDT\",\"ETH_USDT\"]is the trading pair set for the exchange object configured by the bot (in the order of addition and in one-to-one correspondence witheid).
GetAccount()
GetAccount() returns the account information corresponding to the API KEY in the request of the FMZ Quant Trading platform account.
-
Parameter
None -
Return value
{ "code": 0, "data": { "result": { "username": "littlelittledream", "level": 0, "consumed": 3235500000, "invitation_code": "1545967", "points": 25, // The value here, due to precision control, is expressed in integer. To convert it to actual value, you need to divide it by 1e8 (i.e. 10 to the 8th power), and the actual result here is: 65.421 "balance": 6542100000 }, "error": None } }
GetExchangeList()
GetExchangeList() returns the list of supported exchanges and the required configuration information.
-
Parameter
None -
Return value
{ "code": 0, "data": { "result": { "exchanges": [{ "website": "https://www.huobi.pro/", "name": "Huobi", "priority": 1, "meta": "[{"desc": "Access Key", "required": true, "type": "string", "name": "AccessKey", "label": "Access Key"}, {"encrypt": true, "name": "SecretKey", "required": true, "label": "Secret Key", "type": "password", "desc": "Secret Key"}]", "eid": "Huobi", "logo": "huobi.png", "id": 1 }, { "website": "https://www.kex.com/", "name": "KEX", "priority": -99, "meta": "[{"desc": "Access Key", "required": true, "type": "string", "name": "AccessKey", "label": "Access Key"}, {"encrypt": true, "name": "SecretKey", "required": true, "label": "Secret Key", "type": "password", "desc": "Secret Key"}, {"encrypt": true, "required": true, "type": "password", "name": "Password", "label": "Trading Password"}]", "eid": "KEX", "logo": "", "id": 43 }, ... ] }, "error": null } }
DeleteNode(Nid)
DeleteNode(Nid) deletes the docker node (ID is Nid) corresponding to the API KEY in the request of the FMZ Quant Trading platform account.
-
Parameter
Nidis of integer type, namely the dockerID. -
Return Value
{ "code":0, "data":{ "result":true, "error":null } }
DeleteRobot(...)
DeleteRobot(RobotId, DeleteLogs) deletes the robot with the specified ID (robot ID: RobotId) corresponding to the API KEY in the request under the FMZ Quant account.
-
Parameter
RobotIdis of integer type, namely the robotIDto be deleted.
DeleteLogsis of Boolean type; setDeleteLogsto decide whether to delete the log or not; passingtrueindicates deleting the log. -
Return Value
// Return value after successful deletion { "code": 0, "data": { "result": 0, "error": null } }
GetStrategyList()
GetStrategyList() obtains the strategy information corresponding to the API KEY in the request of the FMZ Quant Trading platform account.
-
Parameter
None -
Return value
{ "code": 0, "data": { "result": { "strategies": [{ "category": 0, "username": "yifidslei", "is_owner": true, "name": "fmz simulation market test strategy", "language": 0, "hasToken": false, "args": "[]", "is_buy": false, "public": 0, "last_modified": "2018-01-18 12:36:03", "date": "2018-01-17 09:19:32", "forked": 0, "id": 63372 }, { "category": 20, "username": "bifndslez", "is_owner": true, "name": "Line drawing library", "language": 0, "hasToken": false, "args": "[]", "is_buy": false, "public": 0, "last_modified": "2017-05-08 09:44:18", "date": "2017-04-19 10:38:14", "forked": 0, "id": 39677 }, ... ], "all": 20 }, "error": null } }
NewRobot(Settings)
NewRobot(Settings) creates a new bot according to the parameter settings, corresponding to the API KEY in the request of the FMZ Quant account.
-
Parameter
Settingsis ofJSONobject type.Settingsis aJSONobject configured by the bot.The
Settingsdescription is explained as follows:Settings = { "name": "hedge test", /* Strategy parameters; the order does not have to be in correspondence with the parameter order, but the name must be the same as the parameter name Note: the second element in the parameter array ["MAType", 0, 75882] is an array including three elements, in which the first one "MAType" is the parameter on the pattern referred by the bot-binding strategy, and the second one "0" is the specific value set by the parameter "MAType", and the third one "75882" is the pattern ID containing the parameter "MAType" */ "args": [["Interval", 500], ["MAType", 0, 75882]], // Strategy ID, which can be obtained by "GetStrategyList" method "strategy": 25189, // K-line period parameter; "60" indicates 60 seconds "period": 60, // it can be specified to run on which docker; no writing of the attribute will lead to automatic assignment "node" : 52924, // custom field "appid": "member2", // Specify a bot group "group": 1122, "exchanges": [ // ZB; "pid" can be obtained by "GetPlatformList" method {"pid": 15445, "pair": "ETH_BTC"}, // OKEX {"pid": 13802, "pair": "BCH_BTC"}, // In addition to the exchanges configured by the FMZ dashboard (pid identification), you can also set exchange configuration information that has not been configured to operate the bot {"eid": "OKEX", "pair": "ETH_BTC", "meta" :{"AccessKey": "xxx", "SecretKey": "yyy"}}, {"eid": "Huobi", "pair": "BCH_BTC", "meta" :{"AccessKey": "xxx", "SecretKey": "yyy"}} ] }Note:
When you use the sensitive information, such as platformAPI KEY, including"meta":{"AccessKey":"xxx","SecretKey":"yyy"}in the configuration ofeid, you should know FMZ does not store the data. The data will be sent directly to the docker program, so this information must be configured every time the bot is created or restarted.To restart the bot that uses the plugin to support the platform, When configuring the
Settingsparameter, you should make the following settings for theexchangesattribute:{"eid": "Exchange", "label" : "testXXX", "pair": "ETH_BTC", "meta" :{"AccessKey": "123", "SecretKey": "1234", "Front" : "http://127.0.0.1:6666/XXX"}}labelattribute is to set a label for the exchange object accessed by the current general protocol, which can be obtained by theexchange.GetLabel()function in the strategy. -
Test strategy:
-
Strategy parameter
Interval -
JavaScriptstrategy codejavascriptfunction main(){ Log(exchange.GetAccount()) Log(exchange.GetTicker()) Log(exchange.GetDepth()) Log("Interval:", Interval) }
-
-
Return value
// Create the bot successfully { "code": 0, "data": { "result": 74260, "error": null } }
PluginRun(Settings)
PluginRun(Settings) uses the extended API to call debugging tool function.

-
Parameter
Settingsis aJSONobject, namely the settings in the debugging tool (Settingscontains the test code written in the attributesource). -
Test code
Pythonexample:python#!/usr/bin/python # -*- coding: utf-8 -*- import time import md5 import urllib import json # API KEY has been blurred; you can use your own API KEY to test accessKey = 'f77XXXXXXXXXXXXXXX757' # API KEY has been blurred; you can use your own API KEY to test secretKey = 'd8XXXXXXXXXXXXXXXX41ca97ea15' def api(method, *args): d = { 'version': '1.0', 'access_key': accessKey, 'method': method, 'args': json.dumps(list(args)), 'nonce': int(time.time() * 1000), } d['sign'] = md5.md5('%s|%s|%s|%d|%s' % (d['version'], d['method'], d['args'], d['nonce'], secretKey)).hexdigest() return json.loads(urllib.urlopen('https://www.fmz.com/api/v1', urllib.urlencode(d)).read()) code = ''' function main() { Log(exchange.GetTicker()) exchange.SetTimeout(2000); return exchanges[0].GetTicker() } ''' settings = { # K-line period parameter "60" indicates 60 seconds "period": 60, "source": code, # The docker ID can specify which docker to run the bot on; if the value is -1, it means automatic assignment "node" : 54913, "exchanges": [ {"eid": "OKEX", "pair": "ETH_BTC", "meta" :{"AccessKey": "123abc", "SecretKey": "123abc"}}, {"eid": "Huobi", "pair": "BCH_BTC", "meta" :{"AccessKey": "123abc", "SecretKey": "123abc"}} ] } print api("PluginRun", settings)Note:
{"eid": "OKEX", "pair": "ETH_BTC", "meta" :{"AccessKey": "123abc", "SecretKey": "123abc"}}
{"eid": "Huobi", "pair": "BCH_BTC", "meta" :{"AccessKey": "123abc", "SecretKey": "123abc"}}
For theexchangesattribute in the settings, the attribute only needs to be set to 1, when calling thePluginRuninterface (for only one exchange object can be supported when you use the "Debug Tool" page). No error will be reported when you set 2 exchange objects in settings, but an error will be reported when the second exchange object is accessed in the code. -
Return value
api("PluginRun", settings)returned results:{ u'code': 0, u'data': { u'result': u'{"logs":[{"PlatformId":"","OrderId":"0","LogType":5,"Price":0,"Amount":0,"Extra":"{\\"Info\\":{\\"date\\":\\"1523715057\\",\\"ticker\\":{\\"high\\":\\"0.06400845\\",\\"vol\\":\\"117648.31546800\\",\\"last\\":\\"0.06204514\\",\\"low\\":\\"0.06178666\\",\\"buy\\":\\"0.06200001\\",\\"sell\\":\\"0.06208728\\"}},\\"High\\":0.06400845,\\"Low\\":0.06178666,\\"Sell\\":0.06208728,\\"Buy\\":0.06200001,\\"Last\\":0.06204514,\\"Volume\\":117648.315468,\\"OpenInterest\\":0,\\"Time\\":1523715057726}","Instrument":"","Direction":"","Time":1523715057726}],"result":"{\\"Info\\":{\\"date\\":\\"1523715057\\",\\"ticker\\":{\\"vol\\":\\"117648.31546800\\",\\"last\\":\\"0.06204514\\",\\"low\\":\\"0.06178666\\",\\"buy\\":\\"0.06200001\\",\\"sell\\":\\"0.06208728\\",\\"high\\":\\"0.06400845\\"}},\\"High\\":0.06400845,\\"Low\\":0.06178666,\\"Sell\\":0.06208728,\\"Buy\\":0.06200001,\\"Last\\":0.06204514,\\"Volume\\":117648.315468,\\"OpenInterest\\":0,\\"Time\\":1523715057774}"}\n', u'error': None } }
GetRobotLogs(...)
GetRobotLogs(robotId, logMinId, logMaxId, logOffset, logLimit, profitMinId, profitMaxId, profitOffset, profitLimit, chartMinId, chartMaxId, chartOffset, chartLimit, chartUpdateBaseId, chartUpdateDate, summaryLimit) obtains the robot log information (robot ID: robotId), corresponding to the API KEY in the request of the FMZ Quant account.
-
Parameter
Parameter Name Type Remarks robotId integer Bot ID table Log queries the Log data of the database table:
Parameter Name Type Remarks logMinId integer Minimum ID of the log logMaxId integer Maximum ID of the log logOffset integer After the range is determined by logMinId and logMaxId, the logOffset offset (how many records are skipped) begins to be used as the starting position for obtaining data logLimit integer After determining the starting position, the number of selected data records table Profit queries the Profit data of the database table:
Parameter Name Type Remarks profitMinId integer Minimum ID of record profitMaxId integer Maximum ID of record profitOffset integer The offset (how many records are skipped) begins to be used as the starting position profitLimit integer After determining the starting position, the number of selected data records table Chart queries the Chart data of the database table:
Parameter Name Type Remarks chartMinId integer Minimum ID of record chartMaxId integer Maximum ID of record chartOffset integer Offset chartLimit integer the number of the records to be obtained chartUpdateBaseId integer Query the updated base ID chartUpdateDate integer Data record updates the timestamp, which will filter out the records larger than this timestamp summaryLimit queries the data of the status bar:
It queries the status bar data of the bot. The parameter type is integer. Setting to "0" means there is no need to query the status bar information, and setting to non-zero number indicates the number of bytes of the status bar information to be queried (the interface does not limit the data quantity, so you can specify a larger
summaryLimitparameter to get all status bar information). The status bar data is stored in the returned datasummary.Pythonexample:pythonapi('GetRobotLogs', 63024, 0, 0, 7, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) # For the specific code, please refer to the above content: 4. Simple examples, which will not be repeated here; here only write the call and pass of "GetRobotLogs" -
Return value
returned data:{ "code": 0, "data": { "result": { "status": 1, "updateTime": 1527049990197, "wd": 0, // The first data structure in logs is the log records in the strategy log table in the bot database "logs": [{ "Max": 3984, "Arr": [ [3977, 3, "Futures_OKCoin", "", 0, 0, "Sell(688.9, 2): 20016", 1526954372591, "", ""], [3976, 5, "", "", 0, 0, "OKCoin:this_week too many positions, long: 2", 1526954372410, "", ""] ], "Total": 1503, "Min": 2482 }, { // The second data structure in logs is the log records in the strategy log table in the bot database "Max": 0, "Arr": [], "Total": 0, "Min": 0 }, { // The third data structure in logs is the log records in the strategy log table in the bot database "Max": 0, "Arr": [], "Total": 0, "Min": 0 }], "chart": "", "refresh": 1527049988000, "summary": "...", "chartTime ": 0, "node_id ": 50755, "online ": true }, "error ": null } } -
The strategy log table in the database
The
Arrattribute value description in the above returned result data:"Arr": [ [3977, 3, "Futures_OKCoin", "", 0, 0, "Sell(688.9, 2): 20016", 1526954372591, "", ""], [3976, 5, "", "", 0, 0, "OKCoin:this_week too many positions, long: 2", 1526954372410, "", ""] ],id logType eid orderId price amount extra date contractType direction 3977 3 "Futures_OKCoin" "" 0 0 "Sell(688.9, 2): 20016" 1526954372591 "" "" 3976 5 "" "" 0 0 "OKCoin:this_week too many positions, long: 2" 1526954372410 "" "" extrais the attached message of the printed log.Specific log types represented by
logTypevalue:logType: 0 1 2 3 4 5 6 Meaning of logType: BUY SALE RETRACT ERROR PROFIT MESSAGE RESTART Chinese Meaning Buy order type log Sell order type log Withdraw Error Revenue Log Restart -
Log table of the revenue chart in the database
The data in the chart's log table is consistent with the revenue log in the strategy log table."Arr": [ [202, 2515.44, 1575896700315], [201, 1415.44, 1575896341568] ]Take one of the log data as an example:
[202, 2515.44, 1575896700315]202as logID;2515.44as revenue value;1575896700315as timestamps. -
Chart log table in the database
"Arr": [ [23637, 0, "{\"close\":648,\"high\":650.5,\"low\":647,\"open\":650,\"x\":1575960300000}"], [23636, 5, "{\"x\":1575960300000,\"y\":3.0735}"] ]Take one of the log data as an example:
[23637, 0, "{\"close\":648,\"high\":650.5,\"low\":647,\"open\":650,\"x\":1575960300000}"],23637is the logID,0is the index of the chart data series, and the last data"{\"close\":648,\"high\":650.5,\"low\":647,\"open\":650,\"x\":1575960300000}"is the log data; This data is the K-line data on the chart.
Trading Plugin
Introduction
In order to improve the functions of the trading terminal and well facilitate manual trading, a plug-in function is now available. The location is shown as follows:
Principle of Plugin
The principle is the same as the debugging tool: send a piece of code to the docker of the "Trade" terminal page to execute, and support the return of charts and tables (the debugging tool is also able to support after upgrade). The function is the same as debugging tool, it can only be executed for 3 minutes, without charging. It can realize some simple small functions, complex strategies still need to run in real trading.
Plugin Writing
On the "New Strategy" page, set the strategy type to: Trading Plugin, which supports JavaScript, Python, cpp and MyLanguage.
Usage of Plugin
The plugin can execute the code for a period of time, and it can perform some simple operations, such as iceberg orders, pending orders, order cancellation and order calculation. Same as the debugging tool, it uses return to return the results, and it can also directly return the charts and tables. Here are a few examples, and other functions can be explored by yourself.
-
Return to the depth snapshot
javascript// Return to the depth snapshot function main() { var tbl = { type: 'table', title: 'snapshot of the order depth @ ' + _D(), cols: ['#', 'Amount', 'Ask', 'Bid', 'Amount'], rows: [] } var d = exchange.GetDepth() for (var i = 0; i < Math.min(Math.min(d.Asks.length, d.Bids.length), 15); i++) { tbl.rows.push([i, d.Asks[i].Amount, d.Asks[i].Price+'#ff0000', d.Bids[i].Price+'#0000ff', d.Bids[i].Amount]) } return tbl }pythondef main(): tbl = { "type": "table", "title": "snapshot of the order depth @ " + _D(), "cols": ["#", "Amount", "Ask", "Bid", "Amount"], "rows": [] } d = exchange.GetDepth() for i in range(min(min(len(d["Asks"]), len(d["Bids"])), 15)): tbl["rows"].append([i, d["Asks"][i]["Amount"], str(d["Asks"][i]["Price"]) + "#FF0000", str(d["Bids"][i]["Price"]) + "#0000FF", d["Bids"][i]["Amount"]]) return tblc++void main() { json tbl = R"({ "type": "table", "title": "abc", "cols": ["#", "Amount", "Ask", "Bid", "Amount"], "rows": [] })"_json; tbl["title"] = "snapshot of the order depth @" + _D(); auto d = exchange.GetDepth(); for(int i = 0; i < 5; i++) { tbl["rows"].push_back({format("%d", i), format("%f", d.Asks[i].Amount), format("%f #FF0000", d.Asks[i].Price), format("%f #0000FF", d.Bids[i].Price), format("%f", d.Bids[i].Amount)}); } LogStatus("`" + tbl.dump() + "`"); // C++ does not support "return json" to display the table, and you can create a bot to display the table of the status bar } -
Draw cross-period spreads
javascript// Draw cross-period spreads var chart = { __isStock: true, title : { text : 'spread analysis chart'}, xAxis: { type: 'datetime'}, yAxis : { title: {text: 'spread'}, opposite: false }, series : [ {name : "diff", data : []} ] } function main() { exchange.SetContractType('quarter') var recordsA = exchange.GetRecords(PERIOD_M5) exchange.SetContractType('this_week') var recordsB = exchange.GetRecords(PERIOD_M5) for(var i = 0; i < Math.min(recordsA.length, recordsB.length); i++){ var diff = recordsA[recordsA.length - Math.min(recordsA.length, recordsB.length) + i].Close - recordsB[recordsB.length - Math.min(recordsA.length, recordsB.length) + i].Close chart.series[0].data.push([recordsA[recordsA.length - Math.min(recordsA.length, recordsB.length) + i].Time, diff]) } return chart }pythonchart = { "__isStock": True, "title": {"text": "spread analysis chart"}, "xAxis": {"type": "datetime"}, "yAxis": { "title": {"text": "spread"}, "opposite": False }, "series": [ {"name": "diff", "data": []} ] } def main(): exchange.SetContractType("quarter") recordsA = exchange.GetRecords(PERIOD_M5) exchange.SetContractType("this_week") recordsB = exchange.GetRecords(PERIOD_M5) for i in range(min(len(recordsA), len(recordsB))): diff = recordsA[len(recordsA) - min(len(recordsA), len(recordsB)) + i].Close - recordsB[len(recordsB) - min(len(recordsA), len(recordsB)) + i].Close chart["series"][0]["data"].append([recordsA[len(recordsA) - min(len(recordsA), len(recordsB)) + i]["Time"], diff]) return chartc++// C++ does not support "return json" structure drawingThere are other examples in the "More Strategies", such as buy / sell in small quantities, Strategy Address.
How to Use
-
Add the plugin module of trading terminal
As shown in the figure, open the module add menu on the "Trade" terminal page, the trading terminal plugins in the strategy library of the current FMZ account will be displayed in the list automatically, find the plugin to be added and click "Add". -
Run the plugin
Click "Execute", and the trading terminal plugin will start running. The plugin will not display the log, but it can return display table. -
Time of running the plugin
The maximum running time of the plugin is 3 minutes; and it will stop running automatically after exceeding 3 minutes.
Alpha Factor Analysis Tool
Introduction
The analysis formula refers to the market quote calculation method in the public alpha101 of worldquant: 101_Formulaic_Alphas.pdf, which is basically compatible with its grammar (with explanations for unimplemented features), and has been enhanced.
It is used for quickly performing calculations on time series and validating ideas, Use Address.
Page Introduction:
Functions & Operators
"{}" below represents placeholder, all expressions are not case sensitive, and "x" represents data time series
abs(x), log(x), sign(x)literally means absolute value, logarithm and sign function, respectively.
The following operators, including +, -, *, /, >, <, also meet the meanings of their standards; == represents "equal or not"; || means "or"; x? y: z indicates ternary operator.
rank(x): the ranking of cross-sections, returning the percentage of the location; it is necessary to specify multiple candidate target pools, which cannot be calculated for a single market and will directly return the original result.delay(x, d): the value before the d period of the sequence.sma(x, d): the simple moving average of the d period of the sequence.correlation(x, y, d): the correlation coefficient of time series x and y over the past d periods.covariance(x, y, d): the covariance of the time series x and y in the past d periods.scale(x, a): it normalizes the data, so thatsum(abs(x)) = a("a" defaults to 1).delta(x, d): the current value of the time series x minus the value before d periods.signedpower(x, a):x^a.decay_linear(x, d): weighted d-period moving average of time series x, with weights being d, d-1, d-2... 1 (normalized).indneutralize(x, g): the neutral processing for industry classification "g", currently not supported.ts_{O}(x, d): perform "O" operations on the time series x in the past d periods ("O" can specifically represent "min" and "max", etc., introduced later), "d" will be converted to an integer.ts_min(x, d): the minimum value of the past d periods.ts_max(x, d): the maximum value of the past d periods.ts_argmax(x, d):ts_max(x, d)position.ts_argmin(x, d):ts_min(x, d)position.ts_rank(x, d): sorting of the past d periods time series x values (percentage sorting).min(x, d):ts_min(x, d).max(x, d):ts_max(x, d).sum(x, d): the sum of the past d periods.product(x, d): the product of the past d periods.stddev(x, d): the standard deviation of the past d periods.
Input Data
The input data is not case sensitive; the default data is the selected symbol on the web page, or it can be specified directly, such as binance.ada_bnb
returns: returns of closing price.open, close, high, low, volume: namely open price, close price, highest price, lowest price and trading volume during the period.vwap: volume-weighted executed price, not implemented yet, which is currently the closing price.cap: total market value, not implemented yet.IndClass: industry classification, not implemented yet.
Others
Outputting multiple results (expressed by list) at once is supported; for example, [sma(close, 10), sma(high, 30)] will draw two lines on the chart. In addition to inputting time series data, it can also be used as a simple calculator.
Appendix
General Protocol
For FMZ Quant Trading platform that has not encapsulated the exchange API interface yet, it can be accessed by writing a general protocol plug-in program. You can use this general protocol to access any exchange that provides API interface for trading, and the following two protocols are supported:
RESTprotocol: reference documentation.FIXprotocol: reference item.
The difference between the FIX protocol plug-in program and the REST protocol plug-in program is only the interaction between the protocol plug-in program and the exchange interface. The protocol plug-in program has the same detail processing of the docker program interaction and data format as FMZ Quant. For details, please refer to the examples in the above links.
Trading Terminal
The FMZ Quant Trading platform provides a modular and customizable "Trade" page. You can freely add various data modules and trading function modules, and even develop their own code modules (trading terminal plugins). With its highly flexible and free usage, it also greatly facilitates users of manual trading and semi-programmatic trading.
Various modules on the "Trade" page can be dragged and zoomed, the settings of the trading pairs and exchanges bound by the modules can be modified, and multiple modules of the same type can be added.
Debugging Tool
Debug Tool page provides an environment to quickly test codes by bot, supporting only JavaScript currently.
Remote Editing
It supports the local editor remote synchronization strategy code to FMZ Quant Trading platform, and it supports Sublime Text/Atom/Vim/VSCode editor. On the strategy editing page, click "Remote Edit" to unfold the plugin download address button to display the remote synchronization key (token) of the current strategy.
Click "Update key" to refresh the current key display, and click "Delete Key" to delete the secret key (token) of the current strategy.
The plug-in installation methods of different editors are slightly different. You can click the download button to jump to the specific remote synchronization plug-in item.
Bot Parameters Import & Export
When running the live trading, you need to save the parameter data of the real bot configuration, you can click the "Export" button. The exported strategy parameters will be saved in the JSON file, and the exported strategy parameter configuration can also be imported to the real bot again. Click the "Import" button to import the saved strategy bot parameters to the current real bot. Then, click "Save" to save.
Strategy Import & Export Files
-
Download Source Code
Export the strategy source code, and the type of the export file is based on the strategy programming language. JavaScript strategy exports the files with the extensionjs; python strategy exports the files with the extensionpy; C++ strategy exports the files with the extensioncpp; Mylanguage strategy exports the files with the extensiontxt. Note that only the strategy source code is exported, not including strategy parameters, template references, etc. -
Export Strategy
Export the complete strategy, including all strategy information, such as strategy source code and parameter design. The export file is anxmlfile. -
Import Strategy
Use thexmlfile exported by the "Export" function, and click the "Import" button on the strategy editing page to select thexmlto be imported, that is, you can import a complete strategy. After importing, you need to click the "Save" button to save the strategy.
Multilingual Support
Strategy names and strategy parameter descriptions can be written in Chinese|English, displayed in the language recognized by web pages automatically.
In other places, such as: strategy description, usage instruction and other texts in Markdown format, using English or English can also achieve the effect of automatic recognition. The effect of the above example is shown in the following figure:
After switching the language, it will take effects after refreshing the web page.
Functions that can write strings in the strategy code also support language switching, such as function Log, function LogStatus, etc.
javascript
function main() {
Log("log")
var table = {
type: "table",
title: "option",
cols: ["col1", "col2", "option"],
rows: [
["BTC", "ETH", {"type": "button", "cmd": "coverAll", "name": "平仓|cover", "description": "描述|description"}] // Note: It doesn't need to add tag in the button
]
}
LogStatus("[trans]信息|message", "\n`" + JSON.stringify(table) + "`")
throw "error"
}
python
import json
def main():
Log("log")
table = {
"type": "table",
"title": "option",
"cols": ["col1", "col2", "option"],
"rows": [
["BTC", "ETH", {"type": "button", "cmd": "coverAll", "name": "平仓|cover", "description": "描述|description"}]
]
}
LogStatus("message", "\n`" + json.dumps(table) + "`")
raise Exception("error")
c++
void main() {
Log("log");
json table = R"({
"type": "table",
"title": "option",
"cols": ["col1", "col2", "option"],
"rows": [
["BTC", "ETH", {"type": "button", "cmd": "coverAll", "name": "平仓|cover", "description": "描述|description"}]
]
})"_json;
LogStatus("message", "\n`" + table.dump() + "`");
Panic("error");
}
WexApp Simulation Market
- 24-hour non-stop operation, powerful trading matching system, simulating real trading.
- Powerful market-making bot that provides good liquidity and depth of the order book.
- Complete API interface support, not only can be tested on FMZ Quant for simulated quantitative trading, but also can connect to the API interface by yourself, address: WexApp.
Docker Program Parameters
After downloading the docker software, the executable file after decompression (file name: robot) is the docker program; the parameters can be specified for the docker program, when deploying the docker.
-v: check the information including the version and compilation time of the current docker program.
The complete execution command is based onApple Mac System:./robot -v.-s: the address specified to communicate with FMZ Quant Trading platform when running the docker program.
The complete execution command is based onApple Mac System:./robot -s node.fmz.com/xxxxxxx;xxxxxxxis the unique identification ID of each account on FMZ Quant Trading platform; after executing the command, there will be a prompt to input the password for the corresponding FMZ Quant Trading platform account.-p: you can directly specify the parameter in the run command to input the password, which is not recommended, because the password parameter will be left in the current system record. Suppose the account password corresponding to the addressnode.fmz.com/xxxxxxxis:abc123456.
The complete execution command is based onApple Mac System:./robot -s node.fmz.com/xxxxxxx -p abc123456.-n: attach label information to the running docker program.
The complete execution command is based onApple Mac System:./robot -n macTest -s node.fmz.com/xxxxxxx. There will be amacTesttext label in the docker information on the platform docker management page.-l: print the exchange list supported by the current docker.
The complete execution command is based onApple Mac System:./robot -l, that is, the names of the supported exchanges can be output.
Common Causes for Bot Error & Abnormal Exit
- Strategy static grammar errors (such errors are obvious, and usually you can see the error mark on the strategy editing page), which can be found and corrected during backtest.
- Strategy runtime errors, the most common example is the direct use of the function's return value without making a legal judgment.
- Too much content that cannot be garbage collected is stored in global variables, resulting in excessive memory usage.
- When using the asynchronous
exchange.Gofunction, there is no reasonablewaitto wait for the end of the coroutine during the operation, resulting in a large number of coroutines. - Too many layers of recursive function calls cause the exceeded stack size of the coroutines.
- Interface business errors and network request errors, etc.; such error messages will display the information, including relevant exchange object names, function names, error-related messages and reasons, and other information. Such errors will not cause abnormal stoppage in actual trading (such errors are usually causes, but not the direct causes. The direct causes are usually the program exceptions caused by the direct use without the interface return value to judge the legality).
- Platform underlayer errors, the common example is
Decrypt: Secret key decrypt failed, which will cause the failure of starting the live trading. The reason for the error is that the modification of the FMZ account password causes all the configuredAPI KEYto be invalid. To solve the problem, theAPI KEYneeds to be reconfigured, and the docker needs to be restarted. - When renting a Python strategy, an error is reported due to the version incompatibility between the Python encrypted by the platform and the Python in the strategy runtime:
ValueError: bad marshal data (unknown type code). Upgrade or install the Python environment run by the strategy to one of the versions supported by the strategy:Python 2.7,Python 3.5andPython 3.6. interrupterror; the error is because the user clicks the Stop bot button on the Bot page when the program performs an operation (such as accessing the platform interface), and the bot stops and interrupts the error message printed by the current operation. The error has no effect and is just a log record.
Bots & Strategy Grouping
On the "Bot" page and "Strategy" page of the FMZ Quant Trading platform, you can click Group setting button on the right to manage strategies and real trading in groups. For example, for the group setting of strategies, Template, JavaScript Strategy and Strategy for Backtest can be divided into three groups, respectively.
Sub-Account & Live Bot
Sub-Account
After logging in the platform, click "Dashboard" and "Account" to jump to the FMZ account [management page] (https://www.fmz.com/m/account). Click "Sub-Account" to see the sub-account creation page, select the bot that the added sub-account can access in the available permissions control, and set the sub-account's username and sub-account login password in the user information control. Click the "Add sub-member" button to create a sub-account. The added sub-account will be displayed on the current page and can be "modified", "locked/unlocked", and "deleted".
Sub-accounts have limited permissions; only the authorized bots in the settings of available permissions can be seen in the sub-accounts.
The authorized bot has the authority to modify parameters, stop and restart live trading, but it cannot modify the exchange object configured by the bot. The usage scenarios of sub-accounts are usually:
- A. It is convenient to log in and manage when the quantitative team manages multiple bot strategies.
- B. Debugging when the strategy is for rent.
Live Trading View
In the bot list of the FMZ platform bot page, click the "Public" button to display the bots of the current row publicly. There are currently two ways to view the live bots:
-
- Display the bots on the [Live View] (https://www.fmz.com/live) page published on the FMZ platform, after clicking the "Public" button, and select Public Share.
-
- Create a private link for viewing.
After clicking the "Public" button and selecting Internal Share, you can set a validity period to generate a private link to log in the private live view page of the strategy bot.
- Create a private link for viewing.
Strategy Sharing & Renting
On "Strategy" page, after clicking the "Action" button on the right side of the strategy, the menu that pops up has options for sharing and renting.
Strategy Sharing
-
Public Sharing
After clicking the "Sharing" button, a dialog box will pop up, and you can choose "Public Sharing", so the strategy is completely shared in the platform's "More Strategies", and any user can copy the strategy. -
Internal Sharing
After clicking the "Sharing" button, a dialog box will pop up, and you can choose "Internal Sharing". After selecting the share validity period and the number of shares, the copy page address and copy code of the strategy will be generated. It can be distributed to designated FMZ platform users. Users who need this strategy only need to use the copy page address link, to log in the copy page and enter the copy code to obtain the strategy. After obtaining the strategy, it will appear on the "Strategy" page automatically.
Strategy Renting
-
Public Sale
After clicking the "Rent" button, a dialog box will pop up, and you can choose "Public Sale". The strategy can be applied for listing (subject to approval). -
Internal Sale
After clicking the "Rent" button, a dialog box will pop up, and you can choose "Internal Sale". After selecting the number of days to use, the maximum concurrent number and the number of strategy tokens, the registration page address and strategy token of the strategy will be generated. It can be distributed to specified FMZ users. Users who need this strategy only need to use the registration page address link, to log in to the registration page. Then, enter the strategy token to obtain the right to use the strategy. The strategy will be also displayed on the "Strategy" page, but only with the right to use the backtest and the bot. The information like the source code of the strategy is inaccessible.
Important note:
When creating and distributing the strategy token, please make sure to carefully confirm whether it is a "strategy token" or a "copy code", so as not to share the strategy by mistake.
Sharpe Algorithm in Backtesting System
javascript
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays) {
// force by days
period = 86400000
if (profits.length == 0) {
return null
}
var freeProfit = 0.03 // 0.04
var yearRange = yearDays * 86400000
var totalReturns = profits[profits.length - 1][1] / totalAssets
var annualizedReturns = (totalReturns * yearRange) / (te - ts)
// MaxDrawDown
var maxDrawdown = 0
var maxAssets = totalAssets
var maxAssetsTime = 0
var maxDrawdownTime = 0
var maxDrawdownStartTime = 0
var winningRate = 0
var winningResult = 0
for (var i = 0; i < profits.length; i++) {
if (i == 0) {
if (profits[i][1] > 0) {
winningResult++
}
} else {
if (profits[i][1] > profits[i - 1][1]) {
winningResult++
}
}
if ((profits[i][1] + totalAssets) > maxAssets) {
maxAssets = profits[i][1] + totalAssets
maxAssetsTime = profits[i][0]
}
if (maxAssets > 0) {
var drawDown = 1 - (profits[i][1] + totalAssets) / maxAssets
if (drawDown > maxDrawdown) {
maxDrawdown = drawDown
maxDrawdownTime = profits[i][0]
maxDrawdownStartTime = maxAssetsTime
}
}
}
if (profits.length > 0) {
winningRate = winningResult / profits.length
}
// trim profits
var i = 0
var datas = []
var sum = 0
var preProfit = 0
var perRatio = 0
var rangeEnd = te
if ((te - ts) % period > 0) {
rangeEnd = (parseInt(te / period) + 1) * period
}
for (var n = ts; n < rangeEnd; n += period) {
var dayProfit = 0.0
var cut = n + period
while (i < profits.length && profits[i][0] < cut) {
dayProfit += (profits[i][1] - preProfit)
preProfit = profits[i][1]
i++
}
perRatio = ((dayProfit / totalAssets) * yearRange) / period
sum += perRatio
datas.push(perRatio)
}
var sharpeRatio = 0
var volatility = 0
if (datas.length > 0) {
var avg = sum / datas.length;
var std = 0;
for (i = 0; i < datas.length; i++) {
std += Math.pow(datas[i] - avg, 2);
}
volatility = Math.sqrt(std / datas.length);
if (volatility !== 0) {
sharpeRatio = (annualizedReturns - freeProfit) / volatility
}
}
return {
totalAssets: totalAssets,
yearDays: yearDays,
totalReturns: totalReturns,
annualizedReturns: annualizedReturns,
sharpeRatio: sharpeRatio,
volatility: volatility,
maxDrawdown: maxDrawdown,
maxDrawdownTime: maxDrawdownTime,
maxAssetsTime: maxAssetsTime,
maxDrawdownStartTime: maxDrawdownStartTime,
winningRate: winningRate
}
}
Special Exchange Instructions
-
Futures_Binance
It supports the dual position mode of Binance futures; you can useexchange.IOto switch:javascriptfunction main() { var ret = exchange.IO("api", "POST", "/fapi/v1/positionSide/dual", "dualSidePosition=true") // ret : {"code":200,"msg":"success"} Log(ret) }pythondef main(): ret = exchange.IO("api", "POST", "/fapi/v1/positionSide/dual", "dualSidePosition=false") Log(ret)c++void main() { auto ret = exchange.IO("api", "POST", "/fapi/v1/positionSide/dual", "dualSidePosition=true"); Log(ret); }It supports switching between crossed position/ isolated position
javascriptfunction main() { exchange.SetContractType("swap") exchange.IO("cross", true) // Switch to crossed position exchange.IO("cross", false) // Switch to isolated position }pythondef main(): exchange.SetContractType("swap") exchange.IO("cross", True) exchange.IO("cross", False)c++void main() { exchange.SetContractType("swap"); exchange.IO("cross", true); exchange.IO("cross", false); } -
Futures_HuobiDM
It supports modifying the address of Huobi Futures participating in the signature, which is not switched by default. If you need to disable the function, you can useexchange.IO("signHost", "")to set an empty string.
Useexchange.IO("signHost", "https://aaa.xxx.xxx")to switch the base address of Huobi Futures participating in signature verification.
Useexchange.IO("base", "https://bbb.xxx.xxx")orexchange.SetBase("https://bbb.xxx.xxx")to switch the base address of the platform interface.
When the trading pair is set toXXX_USDT, use the functionexchange.SetContractType("swap")to set the contract code toswapperpetual contract, usingexchange.IO("cross", true)can switch toUSDT-margined perpetual contract in the crossed position mode. Usingexchange.IO("cross", false)to switch back to the isolated position mode. The initial default is the isolated position mode. -
Huobi
It supports Huobi spot leverage tokens, such as:LINK*(-3); the code defined by the exchange is:link3susdt, which is written when FMZ sets the trading pairLINK3S_USDT.
It is also possible to switch trading pairs in the strategy:javascriptfunction main() { exchange.SetCurrency("LINK3S_USDT") Log(exchange.GetTicker()) }pythondef main(): exchange.SetCurrency("LINK3S_USDT") Log(exchange.GetTicker())c++void main() { exchange.SetCurrency("LINK3S_USDT"); Log(exchange.GetTicker()); } -
OKX
OKX interface can switch to the simulation bot testing environment of OKX; usingexchange.IO("simulate", true)can switch to the simulated trading environment. If you want to switch to the real trading environment, useexchange.IO("simulate", false)to switch. The initial default is the real trading environment.
It supports switching account margin modes; useexchange.IO("cross", true)to switch to crossed position mode, and useexchange.IO("cross", false)to switch to isolated position mode, the initial default is the crossed position mode. -
Futures_Bibox
Useexchange.IO("cross", true)to switch to the crossed position mode, and useexchange.IO("cross", false)to switch to the isolated position mode; the initial default is crossed position mode.
The exchange does not support the query of current pending orders and the interface for querying the historical trading records of the market, so theGetOrdersandGetTradesfunctions are not supported. -
Futures_Bitget
Useexchange.IO("cross", true)to switch to crossed position mode, and useexchange.IO("cross", false)to switch to isolated position mode. -
Futures_AOFEX
Useexchange.IO("cross", true)to switch to crossed position mode, and useexchange.IO("cross", false)to switch to isolated position mode. -
Futures_MEXC
Useexchange.IO("cross", true)to switch to crossed position mode, and useexchange.IO("cross", false)to switch to isolated position mode. -
Futures_GateIO
GateIO futures exchange uses leverage value to set crossed position mode, and useexchange.SetMarginLevel(0)to switch to crossed position mode. -
Futures_Bybit
Useexchange.IO("cross", true)to switch to full position mode, useexchange.IO("cross", false)to switch to position-by-position mode. Useexchange.IO("unified", true)to switch to unified margin interface, useexchange.IO("unified", false)to switch back to normal contract interface.
- 1
















































