Type/to search
6
Follow
969
Followers
FMZ API Instructions
Help
Created 2020-04-20 10:19:00  Updated 2023-04-12 14:44:56
 0
 16027

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:

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: TypeScript provides 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:

Set the Python interpreter used by the Python strategy 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 #!python as the version of Python interpreter, and then use , to keep apart; input the encryption command encrypt. If you don't specify the version of Python, add #!,encrypt directly.
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 the RSA KEY authentication 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.
  1. 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 as openssl.
  2. Create an RSA KEY on the exchange, and upload the public key created in Step 1 during creation.
  3. Save the private key created in Step 1 in the same directory of the docker in the format of txt file, or in other paths in the directory of the docker program.
  4. When configuring the exchange on the FMZ platform, fill in the RSA KEY created by the exchange in the edit box of the configuration Access Key.
  5. When configuring the exchange on the FMZ platform, fill in the path of the txt file placed in the same level directory of the docker in the Step 3 in the edit box of the configuration Secret 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 program rsa_key, fill in: file:///rsa_key//rsaKey.txt. If you place rsaKey. txt elsewhere 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)

    NameTypeInstruction
    Bitfinexspot exchange objectsupporting limited trading pairs, such as: BTC_USD, ETH_USD and LTC_USD, etc. (notice the quote currency of the trading pairs is USD dollar)
    Binancespot exchange objectsupporting limited trading pairs, such as: BTC_USDT, ETH_USDT, ETH_BTC and LTC_BTC, etc.
    OKXspot exchange objectsupporting limited trading pairs, such as: BTC_USDT, ETH_USDT, ETH_BTC and LTC_BTC, etc.
    Huobispot exchange objectsupporting limited trading pairs, such as: BTC_USDT, ETH_USDT, ETH_BTC and LTC_BTC, etc.
    OKX Futuresfutures exchange objectsupporting limited trading pairs, such as: BTC_USD and ETH_USD, etc.; the quote currency of the trading pairs is USD; after the setting of specific contract code (please refer to the function exchange.SetContractType), the contract is crypto-margined contract; the supported contract codes include: this_week, next_week, quarter and swap
    HuobiDMfutures exchange objectHuobiDM is Huobi Futures (Huobi Contract), supporting limited trading pairs, such as: BTC_USD and ETH_USD,etc.; the quote currency of the trading pairs is USD; after the setting of specific contract code (please refer to the function exchange.SetContractType), the contract is crypto-margined contract; the supported contract codes include: this_week, next_week, quarter and swap.
    BitMEXfutures exchange objectthe trading pair is XBT_USD; after the setting of specific contract code (please refer to the function exchange.SetContractType), the contract is a crypto-margined contract; the supported contract code is: XBTUSD
    Binance Futuresfutures exchange objectsupporting limited trading pairs, such as: BTC_USDT and ETH_USDT, etc.; the quote currency of the trading pairs is USD; after the setting of specific contract code (please refer to the function exchange.SetContractType), the contract is a USDT - margined contract; the supported contract code is swap
    Deribit Optionsfutures exchange objecttrading pairs are: BTC_USD and ETH_USD; after the setting of specific contract code (please refer to the function exchange.SetContractType), the contract is a crypto-margined contract; specific options contract codes need to be set

    For 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:

img

The backtest of parameters optimization mode:

img

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.

img

Take JavaScript strategy as an example, and click "Save Backtest Settings to Source File":

img

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:

ParameterMeaningExplanation
SymbolSymbol Namesuch as BTC_USD_OKCoin_EN
EidExchangessuch as OKCoin_EN
RoundPrice Accuracysuch as 3, the price in the returned data must be multiplied by 1000 and rounded
VroundQuantity Accuracysuch as 2, the amount in the returned data must be multiplied by 100 and rounded
PeriodBar Period (Milliseconds)such as 60,000 indicating the bar requesting one minute
DepthDepth Levels1-20
TradesWhether Need to Split Datatrue/false
FromStart Timeunix timestamp
ToEnd Timeunix 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

FieldDescription
SchemaIt 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"
DataAn array that stores data by schema

Data Format

FieldDescription
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 key Ctrl, press the key ,.

  • Shortcut key for saving strategy

    Use the key Ctrl + s to save strategies.

  • Shortcut for starting strategy backtest

    Use the key Ctrl + b to enable "Start Backtest".

Code Description

Entry Functions

Function NameDescription
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:
    1. The backtest system doesn't support the function onerror().
    1. If the function onerror() is triggered in the bot, the function onexit() will not be triggered.

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:

    javascript
    function 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) } }
    python
    def 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:

    javascript
    function 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) } }
    python
    def 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:

img

"Template library" in Python:

img

"Template library" in cpp:

img

  • 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() }
    python
    def Test(): Log("template call") # Export "Test" function; the main strategy can be called by ext.Test() ext.Test = Test
    c++
    // 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:

    img

    Template library codes:

    javascript
    $.SetParam1 = function(p1) { param1 = p1 } $.GetParam1 = function() { Log("param1:", param1) return param1 }
    python
    def SetParam1(p1): global param1 param1 = p1 def GetParam1(): Log("param1:", param1) return param1 ext.SetParam1 = SetParam1 ext.GetParam1 = GetParam1
    c++
    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:

    javascript
    function main () { Log("call $.GetParam1:", $.GetParam1()) Log("call $.SetParam1:", "#FF0000") $.SetParam1(20) Log("call $.GetParam1:", $.GetParam1()) }
    python
    def 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()); }

    img

  • Quote "Template library"

    After checking the reference in the template column of the strategy editing page, save the strategy.

    img

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 NameDefinitionValue
ORDER_STATE_PENDINGunfinished0
ORDER_STATE_CLOSEDfinished1
ORDER_STATE_CANCELEDcanceled2
ORDER_STATE_UNKNOWNunknown 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 NameDefinitionValue
ORDER_TYPE_BUYBuy Order0
ORDER_TYPE_SELLSell Order1
Position Type

The attribute Type in the Position structure.

Constant NameDefinitionDescriptionApplicableValue
PD_LONGLong PositionCryptocurrency futures use exchange.SetDirection("closebuy") to set the close position direction, and close this type of positionsCryptocurrency futures0
PD_SHORTShort PositionCryptocurrency futures use exchange.SetDirection("closesell") to set the close position direction, and close this type of positionsCryptocurrency futures1
Futures Opening & Closing Position Directions

The attribute Offset in the Order structure.

Constant NameDefinitionValue
ORDER_OFFSET_OPENOpen Position Orders0
ORDER_OFFSET_CLOSEClose Position Orders1
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:

img

VariableDescriptionRemarksTypeDefault ValueDescription
NumberNumeric typeRemarksNumber (number)1C++ strategy is a floating-point type
StringstringRemarksString (string)Hello FMZThe default value does not need to be quoted. The input is treated as a string
ComboxComboBoxRemarksComboBox (selected)1|2|3The 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
BoolCheck optionsRemarksBoolean (true/false)trueIf checked, the variable bool is true; if not checked, the variable bool is false
SecretStringEncrypted stringRemarksEncrypted String (string)PasswordWith 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.

img

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:

img

When you are using the strategy, the parameters are displayed in groups:

img

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.

img

img

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_Go function is an asynchronous version of function Mail:
    Its usage is similar to the function exchange.Go.

    javascript
    function 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 supported
    c++
    // 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:

img

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 DialSeparate 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 valuecompress 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 valuemode 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 valueproxy 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 valuereconnect 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 valueinterval 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 valuepayload 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 -1 means to return immediately regardless of whether there is any message, such as ws.read(-1).
    Passing in parameter -2 means to return immediately regardless of whether there is any message, but only the latest message is returned, and the message in the buffer will be discarded, such as ws.read(-2).

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

Scenarios \ Read Function parameterNo parameterParameter: -1Parameter: -2Parameter: 2000 (unit: ms)
Buffer already has dataReturn the oldest data immediatelyReturn the oldest data immediatelyReturn the latest data immediatelyReturn the oldest data immediately
No data in the bufferReturn the data when there is data blockedReturn null immediatelyReturn null immediatelyWait 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 reconnectedThe "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:

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

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

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

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

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

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

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

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

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

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

    img

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

Encode(...)

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

The parameter algo is the algorithm used for encoding calculation, which can be set to: "raw" (no algorithm), "sign", "signTx", "md4", "md5", "sha256", "sha512", "sha1", "keccak256", "sha3.224", "sha3.256", "sha3.384", "sha3.512", "sha3.keccak256", "sha3.keccak512", "sha512.384", "sha512.256", "sha512.224", "ripemd160", "blake2b.256", "blake2b.512", "blake2s.128", "blake2s.256". The parameter data is the data to be processed. The inputFormat/outputFormat/keyFormat parameters support encoding methods such as raw, hex, base64 and string.
If the keyFormat parameter is not empty, the key parameter is used for encryption (HMAC), otherwise the default key is used. When the 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 function DBExec, if the sql statement is started with :, the operations in the memory database will be faster without writing files. It is suitable for the database operations without needs for long-term storage, for example:

    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) // 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;")) }
    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) # 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)); }

img

Visualize the simulated data for observation

img

Specific instructions: Built-In Function _Cross Analysis and Instructions

JSONParse(strJson)

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

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:

img

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:

img

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

    javascript
    function main() { var table = { type: 'table', title: 'position operation', cols: ['Column1', 'Column2', 'Action'], rows: [ ['abc', 'def', {'type':'button', 'cmd': 'coverAll', 'name': 'close position'}] ] } var ticker = exchange.GetTicker() // Add a row of data, merge the first and second cells, and output the ticker variable in the merged cell table.rows.push([{body : JSON.stringify(ticker), colspan : 2}, "abc"]) LogStatus('`' + JSON.stringify(table) + '`') }
    python
    import json def main(): table = { "type" : "table", "title" : "position operation", "cols" : ["Column1", "Column2", "Action"], "rows" : [ ["abc", "def", {"type": "button", "cmd": "coverAll", "name": "close position"}] ] } ticker = exchange.GetTicker() table["rows"].append([{"body": json.dumps(ticker), "colspan": 2}, "abc"]) LogStatus("`" + json.dumps(table) + "`")
    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

    javascript
    function 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) + '`') }
    python
    import 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() + "`"); }

img

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:

Highcharts chart

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

Highstocks 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.layout attribute

    If 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.height attribute

    This attribute is used to set the height of the chart. The value can be a numeric type, or set to "300px" mode.

  • cfgA.extension.col attribute

    This 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 JavaScript code part of the example (Complete Example):

    javascript
    cfgC.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 add method, such as adding an item to the pie chart, and here follows the JavaScript code 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 Chart function

    Simple 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) } }
    python
    import 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) } }
    python
    import 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 cpp strategy, the Chart function 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);

Operation Result:
img

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)

    javascript
    c.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 details
    python
    c.barcolor('rgba(255, 0, 0, 0.2)' if bar.Close > bar.Open else 'rgba(0, 0, 0, 0.2)')
    • display optional parameters: "none", "all"
  • bgcolor: Fill the background of the K-line with the specified color

    bgcolor(color, offset, editable, show_last, title, display, overlay)

    javascript
    c.bgcolor('rgba(0, 255, 0, 0.5)')
    python
    c.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)

    javascript
    c.plot(bar.High, 'high')
    python
    h = c.plot(bar.High, 'high')
    • style optional parameters: "stepline_diamond", "stepline", "cross", "areabr", "area", "circles", "columns", "histogram", "linebr", "line"
    • display optional parameters: "none", "all"
  • fill: Fill the background between two drawings or hlines with the provided color

    fill(hline1, hline2, color, title, editable, fillgaps, display)

    javascript
    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)'})
    python
    h = 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)'})
    • display optional parameters: "none", "all"

    Since the JavaScript language 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 the color parameter of the fill function. 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 a title: {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)

    javascript
    c.hline(bar.High)
    python
    c.hline(bar.High)
    • linestyle optional parameters: "dashed", "dotted", "solid"
    • display optional parameters: "none", "all"
  • plotarrow: Draw up and down arrows on the chart

    plotarrow(series, title, colorup, colordown, offset, minheight, maxheight, editable, show_last, display)

    javascript
    c.plotarrow(bar.Close - bar.Open)
    python
    c.plotarrow(bar.Close - bar.Open)
    • display optional parameters: "none", "all"
  • plotshape: Draw visual shapes on the chart

    plotshape(series, title, style, location, color, offset, text, textcolor, editable, size, show_last, display)

    javascript
    c.plotshape(bar.Low, {style: 'diamond'})
    python
    c.plotshape(bar.Low, style = 'diamond')
    • style optional parameters: "diamond", "square", "label_down", "label_up", "arrow_down", "arrow_up", "circle", "flag", "triangle_down", "triangle_up", "cross", "xcross"

    • location optional parameters: "abovebar", "belowbar", "top", "bottom", "absolute"

    • size optional parameters: "10px", "14px", "20px", "40px", "80px", compared with the size.tiny, size.small, size.normal, size.large, size.huge in Pine language
      size.auto is size.small.

    • display optional 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)

    javascript
    c.plotchar(bar.Close, {char: 'X'})
    python
    c.plotchar(bar.Close, char = 'X')
    • location optional parameters: "abovebar", "belowbar", "top", "bottom", "absolute"

    • size optional parameters: "10px", "14px", "20px", "40px", "80px", compared with the size.tiny, size.small, size.normal, size.large, size.huge in Pine language
      size.auto is size.small.

    • display optional 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)

    javascript
    c.plotcandle(bar.Open*0.9, bar.High*0.9, bar.Low*0.9, bar.Close*0.9)
    python
    c.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)

    javascript
    c.signal("long", bar.High, 1.5)
    python
    c.signal("long", bar.High, 1.5)

    The input parameter "long" indicates the transaction direction, optional "long", "closelong", "short", "closeshort". The incoming parameter bar.High is the Y-axis position of the marker signal. The input parameter 1.5 represents 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 NameDescription
GetTickerGet tick quotes data
GetRecordsGet K-line data
GetDepthGet order book data (order depth data)
GetTradesGet 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 TypeVariable NameDescription
numberPriceprice
numberAmountamount

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 of Period only 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 the exchange.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 by exchange.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 exchange corresponds to an exchange with USD as the pricing currency, after calling exchange.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 using exchange.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 the data variable 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:

    img

    As we can see, the corresponding time of timestamp 1579622400000 is 2020-01-22 00: 00: 00; when the strategy program runs after this time, before the next data timestamp 1579708800000, that is, before the time of 2020-01-23 00: 00: 00, call the exchange.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 schema is the data format of each record in the main body of the loaded data, the format is fixed as ["time", "data"], corresponding to the data attribute format of the data one by one. The data attribute 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 Go language:

    golang
    package main import ( "fmt" "net/http" "encoding/json" ) func Handle (w http.ResponseWriter, r *http.Request) { defer func() { fmt.Println("req:", *r) ret := map[string]interface{}{ "schema": []string{"time","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

    javascript
    function main() { Log(exchange.GetData("http://xxx.xx.x.xx:9090/data")) Log(exchange.GetData("https://www.fmz.com/upload/asset/32bf73a69fc12d36e76.json")) }
    python
    def 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 the exchange.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) }
    python
    def 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) }
    python
    def 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)

  • Order structure
  • AvgPrice indicates 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 current exchange objects, will be multiplied by the set exchange rate of 7 for conversion.

  • For example, exchange is an exchange rate denominated in US dollars. After exchange.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")

    javascript
    function 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()) }
    python
    def 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:

      1. 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 to LTC_BTC, not LTC_USDT.
      1. If the websocket protocol mode is switched on the Huobi spot exchange objects, you cannot use exchange.IO("currency", "XXX_YYY") to switch currencies.
      1. 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.
      1. You can also use the new exchange.SetCurrency(Symbol) function to switch trading pairs, and use exchange.IO("currency","XXX_YYY") method to keep compatibility.
  • exchange.IO function switch the exchange API base address (RESET contract; some exchanges do not support that).
    Now the use of exchange.SetBase(Base) function has been supported to switch the exchange API base address, and use exchange.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.

    javascript
    function main () { // exchanges[0] is the first exchange object added when the bot is added exchanges[0].IO("base", "https://api.huobi.pro") }
    python
    def 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.

    javascript
    function main () { exchanges[0].IO("base", "https://api.huobipro.com") }
    python
    def 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:

    javascript
    function 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()) }
    python
    def 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:

    ExchangeRemarks
    Huobi SpotAfter 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. The exchange.GetTicker() and exchange.GetDepth() will be switched to websocket protocol 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 websocket and do not call Sleep function, and test the data immediately. The exchange.GetTicker() and exchange.GetDepth() functions work in cache mode, such as:

    javascript
    function main () { exchange.IO("websocket") while (true) { Log(exchange.GetTicker()) } }
    python
    def 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.
    Use exchange.IO("trade_normal") to switch back to normal account mode.

    Supported exchanges:

    ExchangeSpecial Remarks
    OKXThe 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 and exchange.IO("cross", false) to switch to isolated position.
    HuobiThe 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_margin to switch to a leverage account isolated position; use trade_super_margin to switch to a leverage account crossed position; use trade_normal to switch to a normal currency mode
    ZBThe 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
    BinanceThe leverage accounts are divided into crossed position and isolated position. Use trade_margin to switch to isolated position; use trade_super_margin to switch to crossed position; use trade_normal to switch to a normal currency mode
    GateIOThe leverage accounts are divided into crossed position and isolated position. Use trade_margin to switch to isolated position; use trade_super_margin to switch to crossed position; use trade_normal to switch to a normal currency mode
    AscendExUse exchange.IO("trade_margin") to switch to leverage account mode, and use exchange.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 exchange objects, which is different from the global function Log().
  • Parameter value: LogType can take LOG_TYPE_BUY, LOG_TYPE_SELL, LOG_TYPE_CANCEL and Price as price, and Amount as quantity. When LogType is LOG_TYPE_CANCEL, Price parameter 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.Go function returns an object, the wait function is called to retrieve the data returned by the thread through the object. The thread will be released automatically after the data acquisition by calling wait is completed. If specify the timeout parameter of wait, 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 wait function, and the thread resource applied by the exchange.Go function is released automatically by the docker.
  • If the return result of the wait function 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, use typeof(xx)==="undefined", because null == undefined is 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.

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.

img

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:

img

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 [].

Normally, the cryptocurrency futures contracts are divided into two types:

  • Delivery Contract
    When a contract is set to a delivery contract, call the exchange.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 the exchange.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 FunctionParameter Setting Direction for "SetDirection" FunctionRemarks
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 contract
  • next_week: next week contract
  • quarter: quarterly contract
  • next_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 switching trading pair (or set it directly when adding the exchange objects). For example:

    javascript
    function 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()) }
    python
    def 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 to BTC_USDT directly when you configure the bot parameters and add exchange objects. After switching the trading pair, you need to call exchange.SetContractType function 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 using exchange.SetCurrency("BTC_USDT"), or by setting the current trading pair to BTC_USDT directly when you configure the live trading parameters and add exchange objects. After switching the trading pair, you need to call exchange.SetContractType function again to set the contract.

  • Futures_Deribit
    Set to perpetual contract: exchange.SetContractType("BTC-PERPETUAL")
    Supporting Deribit USDC contract; call exchange.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 to BTC_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 to BTC_USDT, and then set the contract code, which is USDT-margined contract.
      Set to perpetual contract: exchange.SetContractType("swap").
  • 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 the BTC USDT-margined contract, the trading pair is set to BTC_USDT. Binance Futures perpetual contracts also include crypto-margined contracts. For example, in the crypto-margined contract, set the trading pair to BTC_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 of BTC, the trading pair is set to BTC_USD, and then set the contract code exchange.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 of BTC, the trading pair is set to BTC_USD, and then set the contract code exchange.SetContractType("next_quarter). Binance supports part of USDT-margined delivery contracts, such as setting the BTC trading pair to BTC_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 to BTC_USD, indicating crypto-margined contract; the trading pair is set to BTC_USDT, indicating USDT-margined contract. The simulated contracts can set the trading pairs into: SBTC_USD and BTC_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 to BTC_USD, indicating crypto-margined contract; the trading pair is set to BTC_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:

ValueFunction display errorTrigger functionDescription
0Futures_OP 0exchange.SetMarginLevelerror of calling leverage function
1Futures_OP 1exchange.SetDirectionerror of setting futures trading direction function
2Futures_OP 2exchange.SetContractTypeerror of setting contract function
3Futures_OP 3exchange.GetPositionerror of getting position function
4Futures_OP 4exchange.IOerror 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 the exchange.SetContractType function to set the contract as an options contract. After setting up the options contract, when calling the market interface such as GetTicker, all the market data of the options contract is obtained. Use exchange.Sell and exchange.Buy functions to place an order, and pay attention to the trading direction when placing an order, and set the trading direction by exchange.SetDirection. Use the exchange.Cancel function to cancel an order; use the exchange.GetPosition function 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 as Deribit, and the contract code format is BTC-USD-200626-4500-C. You can query the information related to the contract through the interface https://www.okx.com/api/v5/public/instruments.

    For example, to query the information of BTC options contracts:

    javascript
    function main() { Log(HttpQuery("https://www.okx.com/api/v5/public/instruments?instType=OPTION&uly=BTC-USD")) }
    python
    import 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 a BTC contract; 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 to ETH_USDC, call function exchange.SetContractType to 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") (username is user's name, password is 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 of windows system interface version can set directly, which is shown as the following image:

img

Other dockers operated by the command prompt use parameter -I to specify IP addresses:

img

  • 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 Millisecond is millisecond, and 1,000 milliseconds equals 1 second.
  • Only need to set once.
  • Only for the rest protocol.
  • SetTimeout is not a global function, but an exchange object method.

Special Requirements for C++ Written Strategies

The main difference between C++ written strategy and JavaScript written strategy is the returned data differences of FMZ API interface. For example, the exchange.GetTicker() function.

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

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

    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

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

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

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

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

Support for encoding

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

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:

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

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

    1. Variables corresponding to type bytes:
    "0011"

Support for sequential encoding of arrays or types containing arrays:

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 decimals method is a constant method of ERC20 that does not generate gas consumption, and it can query the precision data of a particular token. The decimals method has no parameters. Return value: the precision data of token.

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

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

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

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

  • approve

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

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

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

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

  • multicall

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

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

    Pseudocode is used here to describe some details:

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

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

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

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

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

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

Common function calls

  1. Gets the address of the wallet configured by the exchange object.
javascript
function main() { Log(exchange.IO("address")) // Print the wallet address of the private key configured on the exchange object. }
  1. 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

  • JavaScript library
    http://mathjs.org/

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

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

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

    http://underscorejs.org/

    javascript
    function 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:

    IndicatorDescription
    CDL2CROWSTwo Crows
    CDL2CROWS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDL3BLACKCROWSThree Black Crows
    CDL3BLACKCROWS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDL3INSIDEThree Inside Up/Down
    CDL3INSIDE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDL3LINESTRIKEThree-Line Strike
    CDL3LINESTRIKE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDL3OUTSIDEThree Outside Up/Down
    CDL3OUTSIDE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDL3STARSINSOUTHThree Stars In The South
    CDL3STARSINSOUTH(Records[Open,High,Low,Close]) = Array(outInteger)
    CDL3WHITESOLDIERSThree Advancing White Soldiers
    CDL3WHITESOLDIERS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLABANDONEDBABYAbandoned Baby
    CDLABANDONEDBABY(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger)
    CDLADVANCEBLOCKAdvance Block
    CDLADVANCEBLOCK(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLBELTHOLDBelt-hold
    CDLBELTHOLD(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLBREAKAWAYBreakaway
    CDLBREAKAWAY(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLCLOSINGMARUBOZUClosing Marubozu
    CDLCLOSINGMARUBOZU(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLCONCEALBABYSWALLConcealing Baby Swallow
    CDLCONCEALBABYSWALL(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLCOUNTERATTACKCounterattack
    CDLCOUNTERATTACK(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLDARKCLOUDCOVERDark Cloud Cover
    CDLDARKCLOUDCOVER(Records[Open,High,Low,Close],Penetration = 0.5) = Array(outInteger)
    CDLDOJIDoji
    CDLDOJI(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLDOJISTARDoji Star
    CDLDOJISTAR(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLDRAGONFLYDOJIDragonfly Doji
    CDLDRAGONFLYDOJI(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLENGULFINGEngulfing Pattern
    CDLENGULFING(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLEVENINGDOJISTAREvening Doji Star
    CDLEVENINGDOJISTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger)
    CDLEVENINGSTAREvening Star
    CDLEVENINGSTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger)
    CDLGAPSIDESIDEWHITEUp/Down-gap side-by-side white lines
    CDLGAPSIDESIDEWHITE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLGRAVESTONEDOJIGravestone Doji
    CDLGRAVESTONEDOJI(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHAMMERHammer
    CDLHAMMER(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHANGINGMANHanging Man
    CDLHANGINGMAN(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHARAMIHarami Pattern
    CDLHARAMI(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHARAMICROSSHarami Cross Pattern
    CDLHARAMICROSS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHIGHWAVEHigh-Wave Candle
    CDLHIGHWAVE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHIKKAKEHikkake Pattern
    CDLHIKKAKE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHIKKAKEMODModified Hikkake Pattern
    CDLHIKKAKEMOD(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLHOMINGPIGEONHoming Pigeon
    CDLHOMINGPIGEON(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLIDENTICAL3CROWSIdentical Three Crows
    CDLIDENTICAL3CROWS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLINNECKIn-Neck Pattern
    CDLINNECK(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLINVERTEDHAMMERInverted Hammer
    CDLINVERTEDHAMMER(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLKICKINGKicking
    CDLKICKING(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLKICKINGBYLENGTHKicking - bull/bear determined by the longer marubozu
    CDLKICKINGBYLENGTH(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLLADDERBOTTOMLadder Bottom
    CDLLADDERBOTTOM(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLLONGLEGGEDDOJILong Legged Dojz
    CDLLONGLEGGEDDOJI(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLLONGLINELong Line Candle
    CDLLONGLINE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLMARUBOZUMarubozu
    CDLMARUBOZU(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLMATCHINGLOWMatching Low
    CDLMATCHINGLOW(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLMATHOLDMat Hold
    CDLMATHOLD(Records[Open,High,Low,Close],Penetration = 0.5) = Array(outInteger)
    CDLMORNINGDOJISTARMorning Doji Star
    CDLMORNINGDOJISTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger)
    CDLMORNINGSTARMorning Star
    CDLMORNINGSTAR(Records[Open,High,Low,Close],Penetration = 0.3) = Array(outInteger)
    CDLONNECKOn-Neck Pattern
    CDLONNECK(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLPIERCINGPiercing Pattern
    CDLPIERCING(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLRICKSHAWMANRickshaw Man
    CDLRICKSHAWMAN(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLRISEFALL3METHODSRising/Falling Three Methods
    CDLRISEFALL3METHODS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLSEPARATINGLINESSeparating Lines
    CDLSEPARATINGLINES(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLSHOOTINGSTARShooting Star
    CDLSHOOTINGSTAR(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLSHORTLINEShort Line Candle
    CDLSHORTLINE(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLSPINNINGTOPSpinning Top
    CDLSPINNINGTOP(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLSTALLEDPATTERNStalled Pattern
    CDLSTALLEDPATTERN(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLSTICKSANDWICHStick Sandwich
    CDLSTICKSANDWICH(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLTAKURITakuri (Dragonfly Doji with very long lower shadow)
    CDLTAKURI(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLTASUKIGAPTasuki Gap
    CDLTASUKIGAP(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLTHRUSTINGThrusting Pattern
    CDLTHRUSTING(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLTRISTARTristar Pattern
    CDLTRISTAR(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLUNIQUE3RIVERUnique 3 River
    CDLUNIQUE3RIVER(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLUPSIDEGAP2CROWSUpside Gap Two Crows
    CDLUPSIDEGAP2CROWS(Records[Open,High,Low,Close]) = Array(outInteger)
    CDLXSIDEGAP3METHODSUpside/Downside Gap Three Methods
    CDLXSIDEGAP3METHODS(Records[Open,High,Low,Close]) = Array(outInteger)
  • Volume Indicators:

    IndicatorDescription
    ADChaikin A/D Line
    AD(Records[High,Low,Close,Volume]) = Array(outReal)
    ADOSCChaikin A/D Oscillator
    ADOSC(Records[High,Low,Close,Volume],Fast Period = 3,Slow Period = 10) = Array(outReal)
    OBVOn Balance Volume
    OBV(Records[Close],Records[Volume]) = Array(outReal)
  • Math Transform:

    IndicatorDescription
    ACOSVector Trigonometric ACos
    ACOS(Records[Close]) = Array(outReal)
    ASINVector Trigonometric ASin
    ASIN(Records[Close]) = Array(outReal)
    ATANVector Trigonometric ATan
    ATAN(Records[Close]) = Array(outReal)
    CEILVector Ceil
    CEIL(Records[Close]) = Array(outReal)
    COSVector Trigonometric Cos
    COS(Records[Close]) = Array(outReal)
    COSHVector Trigonometric Cosh
    COSH(Records[Close]) = Array(outReal)
    EXPVector Arithmetic Exp
    EXP(Records[Close]) = Array(outReal)
    FLOORVector Floor
    FLOOR(Records[Close]) = Array(outReal)
    LNVector Log Natural
    LN(Records[Close]) = Array(outReal)
    LOG10Vector Log10
    LOG10(Records[Close]) = Array(outReal)
    SINVector Trigonometric Sin
    SIN(Records[Close]) = Array(outReal)
    SINHVector Trigonometric Sinh
    SINH(Records[Close]) = Array(outReal)
    SQRTVector Square Root
    SQRT(Records[Close]) = Array(outReal)
    TANVector Trigonometric Tan
    TAN(Records[Close]) = Array(outReal)
    TANHVector Trigonometric Tanh
    TANH(Records[Close]) = Array(outReal)
  • Math Operators:

    IndicatorDescription
    MAXHighest value over a specified period
    MAX(Records[Close],Time Period = 30) = Array(outReal)
    MAXINDEXIndex of highest value over a specified period
    MAXINDEX(Records[Close],Time Period = 30) = Array(outInteger)
    MINLowest value over a specified period
    MIN(Records[Close],Time Period = 30) = Array(outReal)
    MININDEXIndex of lowest value over a specified period
    MININDEX(Records[Close],Time Period = 30) = Array(outInteger)
    MINMAXLowest and highest values over a specified period
    MINMAX(Records[Close],Time Period = 30) = [Array(outMin),Array(outMax)]
    MINMAXINDEXIndexes of lowest and highest values over a specified period
    MINMAXINDEX(Records[Close],Time Period = 30) = [Array(outMinIdx),Array(outMaxIdx)]
    SUMSummation
    SUM(Records[Close],Time Period = 30) = Array(outReal)
  • Cycle Indicators:

    IndicatorDescription
    HT_DCPERIODHilbert Transform - Dominant Cycle Period
    HT_DCPERIOD(Records[Close]) = Array(outReal)
    HT_DCPHASEHilbert Transform - Dominant Cycle Phase
    HT_DCPHASE(Records[Close]) = Array(outReal)
    HT_PHASORHilbert Transform - Phasor Components
    HT_PHASOR(Records[Close]) = [Array(outInPhase),Array(outQuadrature)]
    HT_SINEHilbert Transform - SineWave
    HT_SINE(Records[Close]) = [Array(outSine),Array(outLeadSine)]
    HT_TRENDMODEHilbert Transform - Trend vs Cycle Mode
    HT_TRENDMODE(Records[Close]) = Array(outInteger)
  • Volatility Indicators:

    IndicatorDescription
    ATRAverage True Range
    ATR(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    NATRNormalized Average True Range
    NATR(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    TRANGETrue Range
    TRANGE(Records[High,Low,Close]) = Array(outReal)
  • Overlap Studies:

    IndicatorDescription
    BBANDSBollinger Bands
    BBANDS(Records[Close],Time Period = 5,Deviations up = 2,Deviations down = 2,MA Type = 0) = [Array(outRealUpperBand),Array(outRealMiddleBand),Array(outRealLowerBand)]
    DEMADouble Exponential Moving Average
    DEMA(Records[Close],Time Period = 30) = Array(outReal)
    EMAExponential Moving Average
    EMA(Records[Close],Time Period = 30) = Array(outReal)
    HT_TRENDLINEHilbert Transform - Instantaneous Trendline
    HT_TRENDLINE(Records[Close]) = Array(outReal)
    KAMAKaufman Adaptive Moving Average
    KAMA(Records[Close],Time Period = 30) = Array(outReal)
    MAMoving Average
    MA(Records[Close],Time Period = 30,MA Type = 0) = Array(outReal)
    MAMAMESA Adaptive Moving Average
    MAMA(Records[Close],Fast Limit = 0.5,Slow Limit = 0.05) = [Array(outMAMA),Array(outFAMA)]
    MIDPOINTMidpoint over period
    MIDPOINT(Records[Close],Time Period = 14) = Array(outReal)
    MIDPRICEMidpoint Price over period
    MIDPRICE(Records[High,Low],Time Period = 14) = Array(outReal)
    SARParabolic SAR
    SAR(Records[High,Low],Acceleration Factor = 0.02,AF Maximum = 0.2) = Array(outReal)
    SAREXTParabolic 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)
    SMASimple Moving Average
    SMA(Records[Close],Time Period = 30) = Array(outReal)
    T3Triple Exponential Moving Average (T3)
    T3(Records[Close],Time Period = 5,Volume Factor = 0.7) = Array(outReal)
    TEMATriple Exponential Moving Average
    TEMA(Records[Close],Time Period = 30) = Array(outReal)
    TRIMATriangular Moving Average
    TRIMA(Records[Close],Time Period = 30) = Array(outReal)
    WMAWeighted Moving Average
    WMA(Records[Close],Time Period = 30) = Array(outReal)
  • Statistic Functions:

    IndicatorDescription
    LINEARREGLinear Regression
    LINEARREG(Records[Close],Time Period = 14) = Array(outReal)
    LINEARREG_ANGLELinear Regression Angle
    LINEARREG_ANGLE(Records[Close],Time Period = 14) = Array(outReal)
    LINEARREG_INTERCEPTLinear Regression Intercept
    LINEARREG_INTERCEPT(Records[Close],Time Period = 14) = Array(outReal)
    LINEARREG_SLOPELinear Regression Slope
    LINEARREG_SLOPE(Records[Close],Time Period = 14) = Array(outReal)
    STDDEVStandard Deviation
    STDDEV(Records[Close],Time Period = 5,Deviations = 1) = Array(outReal)
    TSFTime Series Forecast
    TSF(Records[Close],Time Period = 14) = Array(outReal)
    VARVariance
    VAR(Records[Close],Time Period = 5,Deviations = 1) = Array(outReal)
  • Momentum Indicators:

    IndicatorDescription
    ADXAverage Directional Movement Index
    ADX(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    ADXRAverage Directional Movement Index Rating
    ADXR(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    APOAbsolute Price Oscillator
    APO(Records[Close],Fast Period = 12,Slow Period = 26,MA Type = 0) = Array(outReal)
    AROONAroon
    AROON(Records[High,Low],Time Period = 14) = [Array(outAroonDown),Array(outAroonUp)]
    AROONOSCAroon Oscillator
    AROONOSC(Records[High,Low],Time Period = 14) = Array(outReal)
    BOPBalance Of Power
    BOP(Records[Open,High,Low,Close]) = Array(outReal)
    CCICommodity Channel Index
    CCI(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    CMOChande Momentum Oscillator
    CMO(Records[Close],Time Period = 14) = Array(outReal)
    DXDirectional Movement Index
    DX(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    MACDMoving Average Convergence/Divergence
    MACD(Records[Close],Fast Period = 12,Slow Period = 26,Signal Period = 9) = [Array(outMACD),Array(outMACDSignal),Array(outMACDHist)]
    MACDEXTMACD 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)]
    MACDFIXMoving Average Convergence/Divergence Fix 12/26
    MACDFIX(Records[Close],Signal Period = 9) = [Array(outMACD),Array(outMACDSignal),Array(outMACDHist)]
    MFIMoney Flow Index
    MFI(Records[High,Low,Close,Volume],Time Period = 14) = Array(outReal)
    MINUS_DIMinus Directional Indicator
    MINUS_DI(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    MINUS_DMMinus Directional Movement
    MINUS_DM(Records[High,Low],Time Period = 14) = Array(outReal)
    MOMMomentum
    MOM(Records[Close],Time Period = 10) = Array(outReal)
    PLUS_DIPlus Directional Indicator
    PLUS_DI(Records[High,Low,Close],Time Period = 14) = Array(outReal)
    PLUS_DMPlus Directional Movement
    PLUS_DM(Records[High,Low],Time Period = 14) = Array(outReal)
    PPOPercentage Price Oscillator
    PPO(Records[Close],Fast Period = 12,Slow Period = 26,MA Type = 0) = Array(outReal)
    ROCRate of change : ((price/prevPrice)-1)*100
    ROC(Records[Close],Time Period = 10) = Array(outReal)
    ROCPRate of change Percentage: (price-prevPrice)/prevPrice
    ROCP(Records[Close],Time Period = 10) = Array(outReal)
    ROCRRate of change ratio: (price/prevPrice)
    ROCR(Records[Close],Time Period = 10) = Array(outReal)
    ROCR100Rate of change ratio 100 scale: (price/prevPrice)*100
    ROCR100(Records[Close],Time Period = 10) = Array(outReal)
    RSIRelative Strength Index
    RSI(Records[Close],Time Period = 14) = Array(outReal)
    STOCHStochastic
    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)]
    STOCHFStochastic Fast
    STOCHF(Records[High,Low,Close],Fast-K Period = 5,Fast-D Period = 3,Fast-D MA = 0) = [Array(outFastK),Array(outFastD)]
    STOCHRSIStochastic 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)]
    TRIX1-day Rate-Of-Change (ROC) of a Triple Smooth EMA
    TRIX(Records[Close],Time Period = 30) = Array(outReal)
    ULTOSCUltimate Oscillator
    ULTOSC(Records[High,Low,Close],First Period = 7,Second Period = 14,Third Period = 28) = Array(outReal)
    WILLRWilliams' %R
    WILLR(Records[High,Low,Close],Time Period = 14) = Array(outReal)
  • Price Transform:

    IndicatorDescription
    AVGPRICEAverage Price
    AVGPRICE(Records[Open,High,Low,Close]) = Array(outReal)
    MEDPRICEMedian Price
    MEDPRICE(Records[High,Low]) = Array(outReal)
    TYPPRICETypical Price
    TYPPRICE(Records[High,Low,Close]) = Array(outReal)
    WCLPRICEWeighted 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:

img

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

img

API Return Code

DescriptionCode
Successful execution0
Wrong API Key1
Wrong signature2
Nonce error3
Incorrect method4
Incorrect parameter5
Internal unknown error6

Bot Status

(Normal start)

StatusCode
Idle0
In operation1
Stopping2
Signed out3
Stopped4
The strategy has errors5

(abnormal)

StatusCode
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 ID of 186515 can receive the interactive instruction: {"close": 39773.75, "name": "aaa"}.

  • Text format:

    BTCUSDTPERP Crossing 39700.00 close: {{close}}

    The bot with ID of 186515 can 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 Python language:

    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 (taking method=GetNodeList as an example):

    https://www.fmz.com/api/v1? access_key=8a1f6c3785fd78a1848320e0b19js99f& nonce=1516292399361& args=%5B%5D& sign=085b63456c93hfb243a757366600f9c2& version=1.0& method=GetNodeList

    Pay attention that there is no secret_key among request parameters.

  • Signature Method
    In the request parameters, the parameter sign is an encrypted string, and the encryption method is as follows.

    According to the format:

    version + "|" + method + "|" + args + "|" + nonce + "|" + secretKey

    After 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 Python code 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, length and robotStatus are of integer type, and label is of string type.

    • offset
    • length
    • robotStatus (passing "-1" means getting all)
    • label (custom label; all bots with the label can be flirted out)

    For Python code example, refer to the above verification method - Token Verification, and the following is the Python invocation example:
    print api('GetRobotList', 'member2') print the information of all robots with the custom label member2.
    print api('GetRobotList', 0, 5, -1, 'member2') pages from 0 to 5 and list up to 5 robots labeled with member2.

  • 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
    RobotId is of integer type, namely the bot ID; Cmd is of string type, which sends interactive commands to bots.

    • RobotId, the bot ID, can be obtained by the GetRobotList(...) interface.
    • Cmd is the interactive command sent to the bot; the command will be captured by the function GetCommand(), which triggers the interactive logic in the strategy (to realize the interactive logic in the strategy, please refer to the GetCommand() 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
    RobotId is of integer type, namely the bot ID, which can be obtained by GetRobotList(...) 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:
      RobotId is of integer type; the robot ID can be obtained by GetRobotList(...).

    • With configuring the bot, the strategy parameter Settings:
      RobotId is of integer type; the bot ID can be obtained by GetRobotList(...).
      Settings is of JSON object type.

      Settings parameter 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 platform API KEY, including "meta":{"AccessKey":"xxx","SecretKey":"yyy"} in the configuration of eid, 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 Settings parameter, you should make the following settings for the exchanges attribute:

      {"eid": "Exchange", "label" : "testXXX", "pair": "ETH_BTC", "meta" :{"AccessKey": "123", "SecretKey": "1234", "Front" : "http://127.0.0.1:6666/XXX"}}

      label attribute is to set a label for the exchange object accessed by the current general protocol, which can be obtained by the exchange.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
    RobotId is of integer type; the robot ID can be obtained by GetRobotList(...).

  • 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 summary attribute 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 the GetRobotLogs interface to get (when GetRobotLogs obtains the information of status bar, the field summary is 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 60 represents the default K-line period set by the bot is 1 minute, namely 60 seconds.

    [44314,42960,15445,14703] is the exchange object eid configured 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 with eid).

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
    Nid is of integer type, namely the docker ID.

  • 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
    RobotId is of integer type, namely the robot ID to be deleted.
    DeleteLogs is of Boolean type; set DeleteLogs to decide whether to delete the log or not; passing true indicates 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
    Settings is of JSON object type. Settings is a JSONobject configured by the bot.

    The Settings description 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 platform API KEY, including "meta":{"AccessKey":"xxx","SecretKey":"yyy"} in the configuration of eid, 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 Settings parameter, you should make the following settings for the exchanges attribute:

    {"eid": "Exchange", "label" : "testXXX", "pair": "ETH_BTC", "meta" :{"AccessKey": "123", "SecretKey": "1234", "Front" : "http://127.0.0.1:6666/XXX"}}

    label attribute is to set a label for the exchange object accessed by the current general protocol, which can be obtained by the exchange.GetLabel() function in the strategy.

  • Test strategy:

    • Strategy parameter
      Interval

    • JavaScript strategy code

      javascript
      function 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
    Settings is a JSON object, namely the settings in the debugging tool (Settings contains the test code written in the attribute source).

  • Test code
    Python example:

    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 the exchanges attribute in the settings, the attribute only needs to be set to 1, when calling the PluginRun interface (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 NameTypeRemarks
    robotIdintegerBot ID

    table Log queries the Log data of the database table:

    Parameter NameTypeRemarks
    logMinIdintegerMinimum ID of the log
    logMaxIdintegerMaximum ID of the log
    logOffsetintegerAfter 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
    logLimitintegerAfter determining the starting position, the number of selected data records

    table Profit queries the Profit data of the database table:

    Parameter NameTypeRemarks
    profitMinIdintegerMinimum ID of record
    profitMaxIdintegerMaximum ID of record
    profitOffsetintegerThe offset (how many records are skipped) begins to be used as the starting position
    profitLimitintegerAfter determining the starting position, the number of selected data records

    table Chart queries the Chart data of the database table:

    Parameter NameTypeRemarks
    chartMinIdintegerMinimum ID of record
    chartMaxIdintegerMaximum ID of record
    chartOffsetintegerOffset
    chartLimitintegerthe number of the records to be obtained
    chartUpdateBaseIdintegerQuery the updated base ID
    chartUpdateDateintegerData 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 summaryLimit parameter to get all status bar information). The status bar data is stored in the returned data summary.

    Python example:

    python
    api('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 Arr attribute 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, "", ""] ],
    idlogTypeeidorderIdpriceamountextradatecontractTypedirection
    39773"Futures_OKCoin"""00"Sell(688.9, 2): 20016"1526954372591""""
    39765""""00"OKCoin:this_week too many positions, long: 2"1526954372410""""

    extra is the attached message of the printed log.

    Specific log types represented by logType value:

    logType:0123456
    Meaning of logType:BUYSALERETRACTERRORPROFITMESSAGERESTART
    Chinese MeaningBuy order type logSell order type logWithdrawErrorRevenueLogRestart
  • 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]

    202 as log ID; 2515.44 as revenue value; 1575896700315 as 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}"],

    23637 is the log ID, 0 is 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:

img

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.

img

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 }
    python
    def 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 tbl
    c++
    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 }

    img

  • 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 }
    python
    chart = { "__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 chart
    c++
    // C++ does not support "return json" structure drawing

    img

    There 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".

    img

  • 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:

img

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 that sum(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:

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.

img

Debugging Tool

Debug Tool page provides an environment to quickly test codes by bot, supporting only JavaScript currently.

img

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.

img

Click "Update key" to refresh the current key display, and click "Delete Key" to delete the secret key (token) of the current strategy.

img

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.

img

Bot Parameters Import & Export

img

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

img

  • 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 extension js; python strategy exports the files with the extension py; C++ strategy exports the files with the extension cpp; Mylanguage strategy exports the files with the extension txt. 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 an xml file.

  • Import Strategy
    Use the xml file exported by the "Export" function, and click the "Import" button on the strategy editing page to select the xml to 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.

img

img

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:

  • Page display in Chinese:
    img

  • Page display in English:
    img

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 on Apple 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 on Apple Mac System: ./robot -s node.fmz.com/xxxxxxx; xxxxxxx is 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 address node.fmz.com/xxxxxxx is: abc123456.
    The complete execution command is based on Apple 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 on Apple Mac System: ./robot -n macTest -s node.fmz.com/xxxxxxx. There will be a macTest text 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 on Apple 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.Go function, there is no reasonable wait to 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 configured API KEY to be invalid. To solve the problem, the API KEY needs 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.5 and Python 3.6.
  • interrupt error; 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:

    1. 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.
    1. 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.

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 use exchange.IO to switch:

    javascript
    function main() { var ret = exchange.IO("api", "POST", "/fapi/v1/positionSide/dual", "dualSidePosition=true") // ret : {"code":200,"msg":"success"} Log(ret) }
    python
    def 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

    javascript
    function main() { exchange.SetContractType("swap") exchange.IO("cross", true) // Switch to crossed position exchange.IO("cross", false) // Switch to isolated position }
    python
    def 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 use exchange.IO("signHost", "") to set an empty string.
    Use exchange.IO("signHost", "https://aaa.xxx.xxx") to switch the base address of Huobi Futures participating in signature verification.
    Use exchange.IO("base", "https://bbb.xxx.xxx") or exchange.SetBase("https://bbb.xxx.xxx") to switch the base address of the platform interface.
    When the trading pair is set to XXX_USDT, use the function exchange.SetContractType("swap") to set the contract code to swap perpetual contract, using exchange.IO("cross", true) can switch to USDT-margined perpetual contract in the crossed position mode. Using exchange.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 pair LINK3S_USDT.
    It is also possible to switch trading pairs in the strategy:

    javascript
    function main() { exchange.SetCurrency("LINK3S_USDT") Log(exchange.GetTicker()) }
    python
    def 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; using exchange.IO("simulate", true) can switch to the simulated trading environment. If you want to switch to the real trading environment, use exchange.IO("simulate", false) to switch. The initial default is the real trading environment.
    It supports switching account margin modes; use exchange.IO("cross", true) to switch to crossed position mode, and use exchange.IO("cross", false) to switch to isolated position mode, the initial default is the crossed position mode.

  • Futures_Bibox
    Use exchange.IO("cross", true) to switch to the crossed position mode, and use exchange.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 the GetOrders and GetTrades functions are not supported.

  • Futures_Bitget
    Use exchange.IO("cross", true) to switch to crossed position mode, and use exchange.IO("cross", false) to switch to isolated position mode.

  • Futures_AOFEX
    Use exchange.IO("cross", true) to switch to crossed position mode, and use exchange.IO("cross", false) to switch to isolated position mode.

  • Futures_MEXC
    Use exchange.IO("cross", true) to switch to crossed position mode, and use exchange.IO("cross", false) to switch to isolated position mode.

  • Futures_GateIO
    GateIO futures exchange uses leverage value to set crossed position mode, and use exchange.SetMarginLevel(0) to switch to crossed position mode.

  • Futures_Bybit
    Use exchange.IO("cross", true) to switch to full position mode, use exchange.IO("cross", false) to switch to position-by-position mode. Use exchange.IO("unified", true) to switch to unified margin interface, use exchange.IO("unified", false) to switch back to normal contract interface.

Related Recommendations
Comment
All comments (0)
No data
No data
  • 1
Forums
PINE Language
Get the app
iPhone Download
© 2015 - ∞ INVENTOR PTE LTD (SG)