[TOC] Before learning this tutorial, you need to study Get Started with FMZ Quant Platform and Elementary Tutorial for FMZ Quant platform Strategy Writing, and become proficient in programming languages. The elementary tutorial covers the most commonly used functions, but there are many functions and features that have not been introduced, and they will not be covered in this tutorial. You need to browse the API document on FMZ platform to understand them by yourself. After learning this tutorial, you will be able to write more free and customized strategies, and FMZ Quant platform is just a tool.
FMZ Quant platform encapsulates all supported platforms. In order to maintain the uniformity, our support for one single platform API is still not complete. For example, GetRecords can pass in the number of K-lines or the starting time, while it’s fixed on FMZ platform; some platforms support batch ordering, while FMZ does not support that, and so on. So there is a need for a way to directly access the platform data. For public interfaces (such as market quotes), you can use HttpQuery
, and for encrypted interfaces (involving account information), you need to use IO
. For specific incoming parameters, please refer to the corresponding platform API document. The previous tutorial has introduced that the Info
field returns raw information, but it still makes no difference on the problem of not supporting interfaces.
It returns the raw content (strings) requested by the last REST API, which can be used to parse the extended information by itself.
function main(){
var account = exchange.GetAccount() //the account doesn't contain all data returned by the request
var raw = JSON.parse(exchange.GetRawJSON())//raw data returned by GetAccount()
Log(raw)
}
To access public interfaces, Js can use HttpQuery
, and Python can use related packages, such as urllib
or requests
.
HttpQuery defaults to the GET method, and supports more functions; check out the API document for more details.
var exchangeInfo = JSON.parse(HttpQuery('https://api.binance.com/api/v1/exchangeInfo'))
Log(exchangeInfo)
var ticker = JSON.parse(HttpQuery('https://api.binance.com/api/v1/ticker/24hr'))
var kline = JSON.parse(HttpQuery("https://www.quantinfo.com/API/m/chart/history?symbol=BTC_USD_BITFINEX&resolution=60&from=1525622626&to=1561607596"))
Example of Python using requests:
import requests
resp = requests.get('https://www.quantinfo.com/API/m/chart/history?symbol=BTC_USD_BITFINEX&resolution=60&from=1525622626&to=1561607596')
data = resp.json()
For interfaces that require API-KEY signatures, the IO function can be used, and the users only need to care about the incoming parameters, and the specific signature process will be completed by the underlayer.
FMZ platform currently does not support BitMEX stop-loss orders, which can be implemented through IO, according to the following steps:
https://www.bitmex.com/api/explorer/
;https://www.bitmex.com/api/v1/order
, with the method of POST
; for FMZ has already internally specified the base address, you only need to pass in “/api/v1/order”.symbol=XBTUSD&side=Buy&orderQty=1&stopPx=4000&ordType=Stop
.Specific Code:
var id = exchange.IO("api", "POST", "/api/v1/order", "symbol=XBTUSD&side=Buy&orderQty=1&stopPx=4000&ordType=Stop")
// You can also pass in the object
var id = exchange.IO("api", "POST", "/api/v1/order", "", JSON.stringify({symbol:"XBTUSD",side:"Buy",orderQty:1,stopPx:4000,ordType:"Stop"}))
More IO examples: https://www.fmz.com/bbs-topic/3683
Basically, all cryptocurrency platforms support websocket to send market quotes, and some platforms support websocket to update account information. Compared with rest API, websocket generally has the advantages, such as low latency, high frequency and not being limited by the frequency of platform rest API, etc. The disadvantage is that there is an interruption problem, whose processing is not intuitive.
This article will mainly introduce how to use JavaScript language and how to use the Dial function encapsulated by the platform to connect, on FMZ Quant platform; for specific instructions and parameters are in the document, you can search for Dial; to realize various functions, the Dial function has been updated several times. This article will cover that and introduces event-driven strategies based on wss, as well as the issue of connecting multiple platforms. Python can also use the Dial function, or the corresponding library.
Generally, directly connect by Websocket; for example, to get Binance tricker push:
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr")
If the returned data is in a compressed format, specification should be made when connecting; “compress” refers to the compressed format, and “mode” represents which returned data needs to be compressed; for example, when connecting with OKEX:
var client = Dial("wss://real.okex.com:10441/websocket?compress=true|compress=gzip_raw&mode=recv")
The Dial function supports reconnection, which is done by the underlying Golang. If the detected connection breaks down, it will be reconnected. For the request data already in the url, such as the example of Binance just now, it is very convenient and recommended. For those who need to send subscription messages, they can maintain the reconnection mechanism themselves.
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr|reconnect=true")
For subscribing to wss messages, some platform requests are in the url, and some need to send the subscribed channels themselves, such as coinbase:
client = Dial("wss://ws-feed.pro.coinbase.com", 60)
client.write('{"type": "subscribe","product_ids": ["BTC-USD"],"channels": ["ticker","heartbeat"]}')
Generally, websocket is used to read market quotes, but it can also be used to get orders and account push. The push of such encrypted data sometimes has a long delay and should be used with caution. Since the encryption method is more complicated, here are a few examples given for reference. Note that only AccessKey is required, which can be set as a strategy parameter. If SecretKey is required, it can be called implicitly by the exchange.HMAC() function to ensure the security.
//Push example of Huobi Futures
var ACCESSKEYID = 'accesskey of your Huobi account'
var apiClient = Dial('wss://api.hbdm.com/notification|compress=gzip&mode=recv')
var date = new Date();
var now_utc = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
var utc_date = new Date(now_utc)
var Timestamp = utc_date.toISOString().substring(0,19)
var quest = 'GET\napi.hbdm.com\n/notification\n'+'AccessKeyId='+ACCESSKEYID+'&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=' + encodeURIComponent(Timestamp)
var signature = exchange.HMAC("sha256", "base64", quest, "{{secretkey} }") // Remove the extra blank spaces between }}
auth = {op: "auth",type: "api",AccessKeyId: ACCESSKEYID, SignatureMethod: "HmacSHA256",SignatureVersion: "2", Timestamp: Timestamp, Signature:encodeURI(signature)}
apiClient.write(JSON.stringify(auth))
apiClient.write('{"op": "sub","cid": "orders","topic": "orders.btc'}')
while (true){
var data = datastream.read()
if('op' in data && data.op == 'ping'){
apiClient.write(JSON.stringify({op:'pong', ts:data.ts}))
}
}
// Push example of Binance; pay attention that listenKey needs to be updated regularly
var APIKEY = 'accesskey of your Binance account'
var req = HttpQuery('https://api.binance.com/api/v3/userDataStream',{method: 'POST',data: ''},null,'X-MBX-APIKEY:'+APIKEY);
var listenKey = JSON.parse(req).listenKey;
HttpQuery('https://api.binance.com/api/v3/userDataStream', {method:'DELETE',data:'listenKey='+listenKey}, null,'X-MBX-APIKEY:'+APIKEY);
listenKey = JSON.parse(HttpQuery('https://api.binance.com/api/v3/userDataStream','',null,'X-MBX-APIKEY:'+APIKEY)).listenKey;
var datastream = Dial("wss://stream.binance.com:9443/ws/"+listenKey+'|reconnect=true',60);
var update_listenKey_time = Date.now()/1000;
while (true){
if (Date.now()/1000 - update_listenKey_time > 1800){
update_listenKey_time = Date.now()/1000;
HttpQuery('https://api.binance.com/api/v3/userDataStream', {method:'PUT',data:'listenKey='+listenKey}, null,'X-MBX-APIKEY:'+APIKEY);
}
var data = datastream.read()
}
// push example of BitMEX
var APIKEY = "your Bitmex API ID"
var expires = parseInt(Date.now() / 1000) + 10
var signature = exchange.HMAC("sha256", "hex", "GET/realtime" + expires, "{{secretkey} }")// secretkey is automatically replaced during execution, so no need to fill in
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)
}
}
Generally, it can be read continuously in an infinite loop. The code is as follows:
function main() {
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr");
while (true) {
var msg = client.read()
var data = JSON.parse(msg) // Parse json strings into quotable objects
// Process data
}
}
The wss data push speed is very fast. The underlayer of Golang will cache all the data in the queue, and when the program calls read, the data will be returned in turn. However, operations such as placing an order on the bot will cause delays, which may result in the accumulation of data. For information such as trading execution push, account push, and depth interpolation push, we need the history data. For market quote data, in most cases, we only care about the latest data, not history data.
If read()
adds no parameters, it will return the oldest data, and block until return when there is no data. If you want the latest data, you can use client.read(-2)
to return the latest data immediately, but when there is no data, it will return null, which needs to be judged before reference.
Depending on how to deal with the old cached data and whether it is blocked when there is no data, “read” has different parameters, as shown in the table below, which looks complicated, but makes the program more flexible.
In this case, it is obvious that simply using “read()” does not work in the program, because one platform will block waiting messages, and another platform will not receive even if there are new messages. The general processing method is:
function main() {
var binance = Dial("wss://stream.binance.com:9443/ws/!ticker@arr");
var coinbase = Dial("wss://ws-feed.pro.coinbase.com", 60)
coinbase.write('{"type": "subscribe","product_ids": ["BTC-USD"],"channels": ["ticker","heartbeat"]}')
while (true) {
var msgBinance = binance.read(-1) // Parameter -1 represents no data and return null immediately; it will not occur that being blocked before there is data to be returned
var msgCoinbase = coinbase.read(-1)
if(msgBinance){
// at this time, Binance has data to return
}
if(msgCoinbase){
// at this time, coinbase has data to return
}
Sleep(1) // Sleep for 1 millisecond
}
}
This part of the processing is more troublesome, because the push data may be interrupted, or the push delay is extremely long. Even if the heartbeat can be received, it does not mean the data is still being pushed. You can set an event interval; if no update is received after the interval, reconnect; it is best to compare the results returned by “rest” after a period of time, to see if the data is accurate. For the special cases of Binance, you can directly set automatic reconnection.
For the push data has been used, the program will naturally be written as event-triggered; pay attention to the frequency of pushing data, because high-frequency requests will lead to being blocked; generally you can write:
var tradeTime = Date.now()
var accountTime = Date.now()
function trade(data){
if(Date.now() - tradeTime > 2000){//Here it limits only one trade in 2 seconds
tradeTime = Date.now()
// Trading logic
}
}
function GetAccount(){
if(Date.now() - accountTime > 5000){//Here it limits GetAccount only once in 5 seconds
accountTime = Date.now()
return exchange.GetAccount()
}
}
function main() {
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr|reconnect=true");
while (true) {
var msg = client.read()
var data = JSON.parse(msg)
var account = GetAccount()
trade(data)
}
}
The connection method, data transmission method, subscribed content and data format of the websocket on each platform are often different, so the platform does not encapsulate it and needs to use the Dial function to connect by itself. This article basically covers some basic precautions. If you have any more questions, please feel free to ask.
PS: Although some platforms do not provide websocket quotes, in fact, when you log in to the website to use the debugging function, you will find that they are all using the websocket push. After researching, you will find out that some subscription formats and return formats seem to be encrypted, which can be seen by decoding and decompressing with base64.
JavaScript can realize concurrence by Go function, and Python can use the corresponding multithread library.
During the realization of quantitative strategies, the concurrent execution can reduce the time delay and improve the efficiency. Take the hedging strategy bot as an example. It needs to obtain the depth of two coins, and the code executed in order is shown as:
var depthA = exchanges[0].GetDepth()
var depthB = exchanges[1].GetDepth()
When a request of rest API is delayed, for example the delayed time is 100 milliseconds, Then the time for obtaining the depth twice is actually different; if more accesses are needed, the delay problems will be more obvious, which will affect the execution of the strategy.
Since JavaScript does not have multithread, the underlayer encapsulates Go function to solve this problem. Go function can be used for the APIs that require network access, such as GetDepth
, GetAccount
and so on. IO
is also supported, such as: exchange.Go("IO", "api", "POST", "/api/v1/contract_batchorder", "orders_data=" + JSON.stringify(orders))
, but due to the design mechanism, it is more tedious to implement.
var a = exchanges[0].Go("GetDepth")
var b = exchanges[1].Go("GetDepth")
var depthA = a.wait() // Call "wait" method to wait for the return of the asynchronous GetDepth result
var depthB = b.wait()
In most simple cases, writing strategies in this way is fine. But note that the process is repeated every time the strategy loops, and the intermediate variables a and b are actually only temporarily auxiliary. If we have a lot of concurrent tasks, we need to additionally record the correspondence between a and depthA, b and depthB. When our concurrent tasks are uncertain, the situation is more complicated. Therefore, we hope to realize a function: when writing Go function concurrently, bind a variable at the same time; when the concurrent running result returns, the result value is automatically assigned to the variable, thus eliminating the need for intermediate variables and making the program more concise. The specific implementation is as follows:
function G(t, ctx, f) {
return {run:function(){
f(t.wait(1000), ctx)
}}
}
We defined a G function, where the parameter “t” is the Go function to be executed, “ctx” is the function of recording program context, and “f” is the function that assigns a specific value. You will see the effect of this function later.
At this time, the overall program framework can be written as a model, similar to the “producer-consumer” model (with some differences), the producer continuously sends out tasks, and the consumer executes them concurrently. The following code is only for demonstration and does not involve any program execution logic.
var Info = [{depth:null, account:null}, {depth:null, account:null}] // If we need to obtain the depth and account of the two platforms, more information can also be put in, such as order ID and status, etc.
var tasks = [ ] // Global task list
function produce(){ // Issue all kinds of concurrent tasks
// Here the task producing logic has been omitted, only for demo
tasks.push({exchange:0, ret:'depth', param:['GetDepth']})
tasks.push({exchange:1, ret:'depth', param:['GetDepth']})
tasks.push({exchange:0, ret:'sellID', param:['Buy', Info[0].depth.Asks[0].Price, 10]})
tasks.push({exchange:1, ret:'buyID', param:['Sell', Info[1].depth.Bids[0].Price, 10]})
}
function worker(){
var jobs = []
for(var i=0;i<tasks.length;i++){
var task = tasks[i]
jobs.push(G(exchanges[task.exchange].Go.apply(this, task.param), task, function(v, task) {
Info[task.exchange][task.ret] = v // Here "v" is the return value of the concurrent Go function "wait()", and you can think about it
}))
}
_.each(jobs, function(t){
t.run() // Here all tasks are executed concurrently
})
tasks = []
}
function main() {
while(true){
produce() // Give trading command
worker() // Concurrently execute
Sleep(1000)
}
}
It seems that only a simple function has been implemented in the above operations. In fact, that has greatly simplified the complexity of the code. We only need to care about what tasks the program needs to generate, and the “worker()” program will automatically execute them concurrently and return the corresponding results. The flexibility has improved a lot.
In the elementary tutorial, drawing class library is recommended in the introduction of drawing, which can meet the needs in most cases. If you need further customization, you can directly operate the “Chart” object.
The internal parameters of Chart({…})
are the objects of HighStock and HighCharts, but an additional parameter __isStock
is added to distinguish whether it is HighStock or not. HighStock focuses more on charts of time series, which is therefore more commonly used. FMZ basically supports the basic modules of HighCharts and HighStock, but does not support additional modules.
The specific HighCharts example: https://www.highcharts.com/demo ; HighStock example: https://www.highcharts.com/stock/demo. You can refer to the codes in the those examples, and transplant them to FMZ conveniently.
You can call add ([series index(like 0), data]) to add data into the series with specified index. Call reset() to clear up the chart data; reset can take a number parameter and specify the amount to be saved. Multiple chart display is supported, which only needs to pass in the array parameters during the configuration, such as: var chart = Chart([{…}, {…}, {…}]). For example, if Chart1 has two series, Chart2 has one series, and Chart3 has one series, when calling add, series ID 0 and 1 are specified to separately represent the data of the two series in the updated Chart1; the series ID 2 is specified to represent the data of the first series in Chart2; the series ID 3 is specified to represent the data of the first series in Chart3.
A specific example:
var chart = { // This "chart" in JS is an object; before using the Chart function, we need to declare the object variable of a configured chart "chart"
__isStock: true, // Mark whether it is a general chart; you can change it to false and try to operate it, if you are interested
tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'}, // Zoom tool
title : { text : 'spread chart'}, // Theme
rangeSelector: { // Choose the range
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
},
xAxis: { type: 'datetime'}, // Horizontal axis, namely X axis; currently set type: time
yAxis : { // Vertical axis, namely Y axis; the default changes according to the data
title: {text: 'spread'}, // Theme
opposite: false, // whether to enable the vertical axis on the right
},
series : [ // Data series; the attribute saves all kinds of data series (lines, K-lines, labels, etc.)
{name : "line1", id : "line1,buy1Price", data : []}, // The index is 0; the data stroed in the data array is the data of the index series
{name : "line2", id : "line2,lastPrice", dashStyle : 'shortdash', data : []}, // The index is 1; set dashStyle: 'shortdash', namely: set dashed line
]
};
function main(){
var ObjChart = Chart(chart); // Call the Chart function, and initialize the chart
ObjChart.reset(); // Empty
while(true){
var nowTime = new Date().getTime(); // Obtain the timestamp of this polling, namely a millisecond tiemstamp, to ensure the location of writing to the X axis in the chart
var ticker = _C(exchange.GetTicker); // Obtain the market quotes data
var buy1Price = ticker.Buy; // Get buy one price from the return value of the market quotes
var lastPrice = ticker.Last + 1; // Get the final executed price, and we add 1 to split the 2 lines
ObjChart.add([0, [nowTime, buy1Price]]); // Use the timestamp as the value of X, and buy one price as the value of Y; pass in the data series of index 0
ObjChart.add([1, [nowTime, lastPrice]]); // Same as above.
Sleep(2000);
}
}
Example of using the chart layout: https://www.fmz.com/strategy/136056
Specific open source address: https://github.com/fmzquant/backtest_python
Installation
Enter the following command in the command line:
pip install https://github.com/fmzquant/backtest_python/archive/master.zip
Simple Example
Set the backtest parameters at the beginning of the strategy code in the form of remark, and the details will be shown at the button of “Save Settings” on the “Edit Strategy” page of FMZ website.
'''backtest
start: 2018-02-19 00:00:00
end: 2018-03-22 12:00:00
period: 15m
exchanges: [{"eid":"OKEX","currency":"LTC_BTC","balance":3,"stocks":0}]
'''
from fmz import *
task = VCtx(__doc__) # initialize backtest engine from __doc__
print exchange.GetAccount()
print exchange.GetTicker()
print task.Join() # print backtest result
Backtest
For complete strategies need infinite loops, the EOF error will be raised after the backtest is over; therefore, we should do fault tolerance well.
# !/usr/local/bin/python
# -*- coding: UTF-8 -*-
'''backtest
start: 2018-02-19 00:00:00
end: 2018-03-22 12:00:00
period: 15m
exchanges: [{"eid":"Bitfinex","currency":"BTC_USD","balance":10000,"stocks":3}]
'''
from fmz import *
import math
import talib
task = VCtx(__doc__) # initialize backtest engine from __doc__
# ------------------------------ Start of the Strategy --------------------------
print exchange.GetAccount() # Call some interfaces, and print their return values
print exchange.GetTicker()
def adjustFloat(v): # the custom functions in the strategy
v = math.floor(v * 1000)
return v / 1000
def onTick():
Log("onTick")
# Specific strategy code
def main():
InitAccount = GetAccount()
while True:
onTick()
Sleep(1000)
# ------------------------------ End of the Strategy --------------------------
try:
main() # The end of the backtest will raise EOFError() to stop stop the backtest loop. Therefore, we should handle with the error, and call task.Join() to print the backtest result, after the error is detected
except:
print task.Join()
exchange.SetData(arr) switches backtest data source and uses custom K-line data. The parameter “arr” is an array, whose elements are K-line bar data (i.e: K-line data array, which temporarily only supports JavaScript backtest.
in the array of arr, the data format of a single element:
[
1530460800, // time Timestamp
2841.5795, // open Open Price
2845.6801, // high Highest Price
2756.815, // low Lowest Price
2775.557, // close Close Price
137035034 // volume Executed Volume
]
The data source can be imported in the “Template”.
function init() { // The init function in the template will be executed first when the template is loaded; ensure the exchange.SetData(arr) function is executed first, initialized, and set the data to the backtest system
var arr = [ // The K-line data to be used during backtest
[1530460800,2841.5795,2845.6801,2756.815,2775.557,137035034], // The data of the earliest K-line bar
... , // If the K-line data is too long, use "..." to represent the omitted data here
[1542556800,2681.8988,2703.5116,2674.1781,2703.5116,231662827] // The data of the latest K-line bar
]
exchange.SetData(arr) // Import the custom data mentioned above
Log("Import data successfully")
}
Note: make sure to import the custom data first (that is, call the exchange.SetData function to set the data) during initialization. The custom K-line data period must be consistent with the underlayer K-line period set on the backtest page, that is: custom K-line data ; the time of a K-line is 1 minute, so the period of the underlayer K-line set in the backtest should also be set to 1 minute.
If the API of an unsupported platform is exactly the same as that of a supported platform, except the base address,the unsupported platform can be supported by switching the base address. To be specific, select a supported platform when adding a platform, but fill in the the API-KEY of the unsupported platform, and use IO to switch the base address in the strategy, such as:
exchange.IO("base", "http://api.huobi.pro")
//http://api.huobi.pro is the base address of the unsupported platform API, and notice not to add api/v3 and so on, for the address will be automatically completed
Not all platforms are supported by FMZ, but our platform has provided the access method of general protocol. Specific principles are:
Simply speaking, general protocol is like an intermediary, proxying the request of the docker and returning the data , according to the corresponding standard. The code of the general protocol needs to be completed by yourself. Writing the general protocol actually means that you can access the platform solely and complete the strategy. FMZ official sometimes releases the exe version of the general protocol of platforms. The general protocol can also be done in Python, which can then be run on the docker as an ordinary bot.
Specific introduction of general protocol: https://www.fmz.com/bbs-topic/9120 Example of writing General protocol in Python: https://www.fmz.com/strategy/101399
Just like various operations of a platform can be implemented through API, FMZ website is also based on API. You can apply for your own API-KEY of FMZ website to realize functions, such as “create”, “restart”, DeleteRobot, GetRobotList GetRobotLogs, etc. Please refer to the “API Extension of FMZ Platform” section in the API document for details.
Due to the powerful extensibility of FMZ Quant platform, you can create your own quantitative platform based on the API extension, allowing your users to run bots on your platform, etc. Specific reference: https://www.fmz.com/bbs-topic/1697 .
The cryptocurrency trading market has attracted more and more attention from quantitative traders due to its particularity. In fact, program trading has become the mainstream of cryptocurrency, and strategies such as hedging and market making are always active in the market. Beginners with weak programming foundation want to enter into this new field, confronting with numerous platforms and changing APIs, full of difficulties. FMZ Quant Platform, (former BotVs, www.fmz.com) currently the largest cryptocurrency quantitative community and platform, has helped thousands of beginners on the road to quantitative trading for more than 4 years. To take the course on NetEase, it only takes you 20 yuan, and the course is totally for beginners.
Promotion of Cryptocurrency Quantitative Trading Course on NetEase Cloud Classroom. Log in to NetEase Cloud Classroom and share your course link (the link has a unique courseId). Others, who register and purchase the course through this link, will bring you 50% of the total as a commission, namely 10 yuan. Follow the WeChat public account of “NetEase Cloud Classroom Premium Course Promotion” to withdraw the cash. You are also welcome to invite others to promote the course in Weibo or QQ group.
Consumers who click on the promotion link, register and recharge within half a year, will enjoy the policy that our company will rebate according to the effective amount in the valid order. The commission will be returned to the promoter’s account in the form of points. Users can exchange the points to the account balance of FMZ platform at a ratio of 10:1, and users can also use the points to exchange the related products of FMZ Quant in the future. Specific link for the activity: https://www.fmz.com/bbs-topic/3828
The complete FMZ website can be deployed to the exclusive server of an enterprise or a team for complete control and functional customization. The FMZ website has been used and tested by about 100,000 users, and has achieved high availability and security, which can save time for quantitative teams and enterprises. The enterprise version is for medium-sized quantitative trading teams, commodity futures service providers, etc. Please contact the administrator for specific quotations.
The professional system, providing the market liquidity and funds management for platforms, might be the most improved market making system in the market. It is widely used by many platforms and teams.
FMZ technology trading system adopts memory matching technology, and the order processing speed is as high as 2 million trades per second, which can ensure that there will be no delay or lag in the order processing. It can maintain the smooth and stable operation of platforms with more than 20 million simultaneous online users. The multi-layer and multi-cluster system framework ensures the security, stability and extensibility of the system. Function deployment and version updates can be carried out without downtime, which maximum guarantees the operating experience of the terminal users. At present, the system can be experienced in the wex.app simulated platform.