How to write a step-by-step tutorial for the FMZ Quantified Platform Strategy

Author: The grass, Created: 2019-08-19 15:54:35, Updated: 2021-06-08 16:14:57

[TOC] Before you learn this tutorial, you need to learn.FMZ inventors use the quantification platform to get startedandHow to write an elementary tutorial for the FMZ Quantified Platform StrategyHe is also proficient in programming languages.The basic tutorial deals with the most commonly used functions, but there are many more functions and functions that are not covered in this tutorial, and you need to browse the platform API documentation for yourself.After learning this tutorial, you will be able to write freer, more customized strategies, and the FMZ platform is just a tool.

Access the raw data of the exchange

The FMZ platform is wrapped for all supported exchanges, and in order to maintain uniformity, the API support for individual exchanges is not complete. For example, obtaining a K-line can generally transmit the number of K-lines or the start time, while the FMZ platform is fixed, some platforms support bulk ordering, FMZ does not support, etc. Therefore, a way to directly access the exchange data is needed.It can be used for open interfaces (e.g. industry).HttpQuery, for the addition of close-up (http://www.account.com/) information, you need to useIOFor specific input parameters, please refer to the corresponding exchange API documentation.InfoThe field returns the original information, but still fails to resolve the issue of not supporting the interface.

GetRawJSON()

Returns the original content (string) returned by the last REST API request, which can be used to parse the extension information 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)
}

HttpQuery ((() to access the public interface

Access the public interface that Js can useHttpQueryPython can also be used independently with related packages, such asurlliborrequests

HttpQuery is the default method of GET, but it also supports more features. See the API documentation 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"))

Examples 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()

The IO function accesses the closest port

For interfaces that require API-KEY signatures, IO functions can be used, users only need to worry about passing parameters, and the specific signing process will be completed by the bottom layer.

The FMZ platform does not currently support BitMEX stop loss orders, but can be implemented via IO using the following steps:

  • You can find the BitMEX API page:https://www.bitmex.com/api/explorer/
  • BitMEX can be found at:https://www.bitmex.com/api/v1/orderThe method isPOST│ Since FMZ has already specified the root address internally, all you need to do is enter "/api/v1/order".│
  • The corresponding parameterssymbol=XBTUSD&side=Buy&orderQty=1&stopPx=4000&ordType=Stop

The specific code:

var id = exchange.IO("api", "POST", "/api/v1/order", "symbol=XBTUSD&side=Buy&orderQty=1&stopPx=4000&ordType=Stop")
//也可以直接传入对象
var id = exchange.IO("api", "POST", "/api/v1/order", "", JSON.stringify({symbol:"XBTUSD",side:"Buy",orderQty:1,stopPx:4000,ordType:"Stop"}))

More examples of IO:https://www.fmz.com/bbs-topic/3683

Using the websocket

Virtually all digital currency exchanges support websocket sending, and some exchanges support websocket updates of account information. Compared to rest API, websocket generally has a low latency, high frequency, and is not subject to platform rest API frequency limitations. The disadvantage is that there are interruption problems, processing is not intuitive.

This article mainly covers the FMZ inventor quantization platform, using the JavaScript language, using the platform-wrapped Dial function for connections, specifications and parameters in the documentation, searching Dial, several updates of the Dial function to implement various functions, this article will cover this, as well as introducing the strategy of event-driven connection based on wss, and the problem of connecting multiple exchanges. Python can also use the Dial function, and the corresponding library can be used.

1.websocket连接

In general, a direct connection is possible, such as getting a security ticker pushed:

var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr")

For the returned data to be compressed in the format that is required at the connection, compress specifies the compression format, the mode represents sending back the data that needs to be compressed, such as connection 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 Go language, the detected connection disconnection will reconnect, for the requested data content already in the url, such as the example of Binance, it is very convenient, recommended to use. For those who need to send an order message, you can maintain the reconnection mechanism yourself.

var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr|reconnect=true")

Subscribe to the WS messages, some exchange requests are in the url, and some channels require you to send your own subscription, such as Coinbase:

client = Dial("wss://ws-feed.pro.coinbase.com", 60)
client.write('{"type": "subscribe","product_ids": ["BTC-USD"],"channels": ["ticker","heartbeat"]}')

2.加密接口连接

Websockets are commonly used to read transactions, but they can also be used to obtain orders and account pushes. The push of this type of encrypted data can sometimes take a long time, and should be used with caution. Due to the complexity of the encryption method, here are some examples.

    //火币期货推送例子
    var ACCESSKEYID = '你的火币账户的accesskey'
    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} }") //去掉}}之间的多余空格
    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}))
        }
    }
    
    //币安推送例子,注意需要定时更新listenKey
    var APIKEY = '你的币安accesskey'
    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()
    }

    //BitMEX推送例子
    var APIKEY = "你的Bitmex API ID"
    var expires = parseInt(Date.now() / 1000) + 10
    var signature = exchange.HMAC("sha256", "hex", "GET/realtime" + expires, "{{secretkey} }")//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)
        }
    }

3.websocket读取

The code is usually read continuously during the death cycle, 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) //把json字符串解析为可引用的object
// 处理data数据
    }
}

The wss data is pushed very fast, the bottom layer of Go will put all the data in the queue, waiting for the program to call read, and then return it; while the downlist on the physical disk will cause delays, which may cause data accumulation. For transaction push, account push, depth push, etc. information, we need historical data, for market data, we are mostly concerned only with the latest, not with historical data.

read()If no parameters are added, the oldest data will be returned, and if no data is available, the return will be blocked.client.read(-2)Returns the latest data immediately, but returns null when no more data is available, and needs to be re-referenced.

Read has different parameters depending on how old data is treated in the cache, and whether it gets clogged when there is no data, specifically as shown below, which looks complicated but makes the program more flexible.img

4.连接多个交易所websocket

In this case, it is obvious that the procedure cannot use a simple read (), because one exchange will block the waiting message, at which point the other exchange will not receive it even if there is a new message.

    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) // 参数-1代表无数据立即返回null,不会阻塞到有数据返回
            var msgCoinbase = coinbase.read(-1)
            if(msgBinance){
                // 此时币安有数据返回
            }
            if(msgCoinbase){
                // 此时coinbase有数据返回
            }
            Sleep(1) // 可以休眠1ms
        }
    }

5.断线重连问题

This part of the process is more troublesome, because the push data can be interrupted, or the push delay is extremely high, even if the heartbeat is not received, it does not mean that the data is still being pushed, you can set an event interval, reconnect if you do not receive an update if you exceed the interval, and it is preferable to compare the results of the rest return with the rest of the time to see if the data is accurate.

6.使用websocket的一般程序框架

Since the push data has already been used, the program will naturally also be written as an event driver, pay attention to pushing data frequently, so as not to use too many requests that will lead to being closed, generally you can write:

    var tradeTime = Date.now()
    var accountTime = Date.now()
    function trade(data){
        if(Date.now() - tradeTime > 2000){//这里即限制了2s内只交易一次
            tradeTime = Date.now()
            //交易逻辑
        }
    }
    function GetAccount(){
        if(Date.now() - accountTime > 5000){//这里即限制了5s内只获取账户一次
            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)
        }
    }

7.总结

The way the websockets are connected, the way data is sent, the content that can be subscribed to, and the data format are often different, so the platform is not wrapped and needs to connect itself with the Dial function. This article basically covers some basic precautions, and if there are any questions, please ask.

PS. Some exchanges do not offer a websocket market, but they actually use a websocket push function to land on a website. Research shows that the subscription format and return format are used. Some look like they are encrypted, and can be seen by decrypting them with base64 decryption.

Multi-threaded concurrency

JavaScript can be implemented concurrently with the Go function, and Python can use the corresponding multi-threaded library.

When implementing a quantification strategy, in many cases, concurrent execution can reduce the efficiency of the delayed boost. For example, a hedge strategy requires the depth of two coins, and the sequence of execution is as follows:

var depthA = exchanges[0].GetDepth()
var depthB = exchanges[1].GetDepth()

Once a request for rest API is delayed, assuming 100ms, then the time taken to obtain depth twice is actually different, and if more access is required, the delay problem will be more prominent, affecting the execution of the policy.

JavaScript has no multiple threads, so the bottom layer encapsulates the Go function to solve this problem. The Go function can be used for APIs that require network access, such asGetDepth,GetAccountAnd so on.IOIt's called:exchange.Go("IO", "api", "POST", "/api/v1/contract_batchorder", "orders_data=" + JSON.stringify(orders))But due to the design mechanism, implementation is more cumbersome.

var a = exchanges[0].Go("GetDepth")
var b = exchanges[1].Go("GetDepth")
var depthA = a.wait() //调用wait方法等待返回异步获取depth结果 
var depthB = b.wait()

In most simple cases, writing a policy like this is not a problem. But note that each policy loop repeats this process, and the intermediate variables a and b are actually only temporary auxiliaries. If we have a very large number of concurrent tasks, we also need to record the corresponding relationship between a and depthA, b, and depthB. The situation is more complex when our concurrent tasks are uncertain.

function G(t, ctx, f) {
    return {run:function(){
        f(t.wait(1000), ctx)
    }}
}

We define a G function, where the argument t is the Go function to be executed, ctx is the context of the recording program, and f is the function with the specific assignment.

In this case, the overall programming framework can be written as similar to the producer-consumer model (with some differences), where the producer continuously issues tasks and the consumer executes them simultaneously, while the code is only a demonstration and does not involve the execution logic of the program.

var Info = [{depth:null, account:null}, {depth:null, account:null}] //加入我们需要获取两个交易所的深度和账户,跟多的信息也可以放入,如订单Id,状态等。
var tasks = [ ] //全局的任务列表

function produce(){ //下发各种并发任务
  //这里省略了任务产生的逻辑,仅为演示
  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 //这里的v就是并发Go函数wait()的返回值,可以仔细体会下
                }))
    }
    _.each(jobs, function(t){
            t.run() //在这里并发执行所有任务
        })
    tasks = []
}
function main() {
    while(true){
        produce()         // 发出交易指令
        worker()        // 并发执行
        Sleep(1000)
    }
}

It seems that a loop only achieves a simple function, in fact greatly simplifies the complexity of the code, we only need to worry about what tasks the program needs to produce, and the worker () program automatically executes them simultaneously and returns the corresponding results. Flexibility is greatly improved.

Chart function drawing

The basic tutorial introduces the chart is a recommended chart library, which can be used in most cases. If further customization is needed, the Chart object can be directly operated.

Chart({…})The internal parameters are HighStock and HighCharts objects, just adding an additional parameter__isStockHighStock is more focused on time series charts and is therefore more commonly used. FMZ basically supports the basic modules of HighCharts and HighStock, but does not support additional modules.

High Charts examples:https://www.highcharts.com/demoHighStock example:https://www.highcharts.com/stock/demoThe code of these examples can be easily ported to FMZ.

Add (([series index ((e.g. 0, data]) can be called to add data to the series of the specified index, call reset (()) to empty the graph data, reset can be a numeric parameter, specify the reserved entry. It supports displaying multiple graphs, configuring simply by passing array parameters such as: var chart = Chart (([{...}, {...}, {...})), for example, graph one has two series, graph one has a series, graph three series, then add when specifying a 01 sequence with ID series represents the data of the two series of the updated graph 1, add when specifying the sequence ID series 2 refers to the data of the first series of graph 2, specifying the sequence ID 3 refers to the data of the first series of the graph 3;

A specific example:

var chart = { // 这个 chart 在JS 语言中 是对象, 在使用Chart 函数之前我们需要声明一个配置图表的对象变量chart。
    __isStock: true,                                    // 标记是否为一般图表,有兴趣的可以改成 false 运行看看。
    tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'},    // 缩放工具
    title : { text : '差价分析图'},                       // 标题
    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
    },
    xAxis: { type: 'datetime'},                         // 坐标轴横轴 即:x轴, 当前设置的类型是 :时间
    yAxis : {                                           // 坐标轴纵轴 即:y轴, 默认数值随数据大小调整。
        title: {text: '差价'},                           // 标题
        opposite: false,                                // 是否启用右边纵轴
    },
    series : [                                          // 数据系列,该属性保存的是 各个 数据系列(线, K线图, 标签等..)
        {name : "line1", id : "线1,buy1Price", data : []},  // 索引为0, data 数组内存放的是该索引系列的 数据
        {name : "line2", id : "线2,lastPrice", dashStyle : 'shortdash', data : []}, // 索引为1,设置了dashStyle : 'shortdash' 即:设置 虚线。
    ]
};
function main(){
    var ObjChart = Chart(chart);  // 调用 Chart 函数,初始化 图表。
    ObjChart.reset();             // 清空
    while(true){
        var nowTime = new Date().getTime();   // 获取本次轮询的 时间戳,  即一个 毫秒 的时间戳。用来确定写入到图表的X轴的位置。
        var ticker = _C(exchange.GetTicker);  // 获取行情数据
        var buy1Price = ticker.Buy;           // 从行情数据的返回值取得 买一价
        var lastPrice = ticker.Last + 1;      // 取得最后成交价,为了2条线不重合在一起 ,我们加1
        ObjChart.add([0, [nowTime, buy1Price]]); // 用时间戳作为X值, 买一价 作为Y值 传入 索引0 的数据序列。
        ObjChart.add([1, [nowTime, lastPrice]]); // 同上。
        Sleep(2000);
    }
}

Here is an example of using a graphical layout:https://www.fmz.com/strategy/136056

Re-testing the steps

Python locally retrieves

The specific open source address is:https://github.com/fmzquant/backtest_python

Installed

Enter the following command in the command line:

pip install https://github.com/fmzquant/backtest_python/archive/master.zip

A simple example

The retrieval parameter is set in the form of a comment at the beginning of the policy code, for example, see the FMZ website's policy editor interface to save retrieval settings.

'''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

Reassessment

Since the complete strategy requires a dead-loop, the EOF anomaly will be discarded at the end of the retest to terminate the procedure, so it is necessary to be tolerant of mistakes.

# !/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__

# ------------------------------ 策略部分开始 --------------------------

print exchange.GetAccount()     # 调用一些接口,打印其返回值。
print exchange.GetTicker()

def adjustFloat(v):             # 策略中自定义的函数
    v = math.floor(v * 1000)
    return v / 1000

def onTick():
    Log("onTick")
    # 具体的策略代码


def main():
    InitAccount = GetAccount()
    while True:
        onTick()
        Sleep(1000)

# ------------------------------ 策略部分结束 --------------------------

try:
    main()                     # 回测结束时会 raise EOFError() 抛出异常,来停止回测的循环。所以要对这个异常处理,在检测到抛出的异常后调用 task.Join() 打印回测结果。
except:
    print task.Join()         

Customized retrieval data

exchange.SetData ((arr), switching back to a query data source using custom K-line data. The argument arr, is an elementary array of K-line column data ((i.e.: K-line data arrays, which temporarily only supports JavaScript query).

In the arr array, the data format of the single element is:

[
    1530460800,    // time     时间戳
    2841.5795,     // open     开盘价
    2845.6801,     // high     最高价
    2756.815,      // low      最低价
    2775.557,      // close    收盘价
    137035034      // volume   成交量
]

Data sources can be imported into the template library.

function init() {                                                          // 模板中的 init 初始化函数会在加载模板时,首先执行,确保 exchange.SetData(arr) 函数先执行,初始化,设置数据给回测系统。
    var arr = [                                                            // 回测的时候需要使用的K线数据
        [1530460800,2841.5795,2845.6801,2756.815,2775.557,137035034],      // 时间最早的一根 K线柱 数据
        ... ,                                                              // K线数据太长,用 ... 表示,数据此处省略。
        [1542556800,2681.8988,2703.5116,2674.1781,2703.5116,231662827]     // 时间最近的一根 K线柱 数据
    ]
    exchange.SetData(arr)                                                  // 导入上述 自定义的数据
    Log("导入数据成功")
}

Note: It is necessary to import the custom data first at initialization (i.e. call the data set by the exchange.SetData function), and the custom K-line data cycle must match the underlying K-line cycle set by the retest page, i.e. if the custom K-line data is 1 minute, then the underlying K-line cycle set in the retest is also set to 1 minute.

Use FMZ-supported exchanges

If the unsupported exchange and the supported exchange API are exactly the same, only the base address is different, it can be supported by switching the base address. Specifically, when adding an exchange, select the supported exchange, but API-KEY fills in the unsupported exchange, in the policy, use IO to switch the base address, such as:

exchange.IO("base", "http://api.huobi.pro") 
//http://api.huobi.pro为为支持交易所API基地址,注意不用添加/api/v3之类的,会自动补全

Not all exchanges support FMZ, but the platform provides access to the general protocol.

  • If you write your own code to access the exchange, the program will create a web service.
  • Add an exchange to the FMZ platform, specify the address and port of the network service.
  • When the custodian runs the virtual disk of the exchange on the universal protocol, the API access in the policy is sent to the universal protocol.
  • The General Protocol visits the exchange upon request and returns the results to the custodian.

In simple terms, a general protocol is equivalent to an intermediary that acts as an agent for a host's request and returns data according to the corresponding standards. The code of a general protocol needs to be completed by itself, and writing a general protocol actually represents that you can access the exchange separately and complete the policy. FMZ officials sometimes release an exe version of the exchange.

The specific agreement:https://www.fmz.com/bbs-topic/1052Here are some examples of common Python protocols:https://www.fmz.com/strategy/101399

Creating your own quantification platform

Just like the exchange, the FMZ website is also API-based. You can apply for your own FMZ website API-KEY implementation, such as creating, restarting, deleting disk, accessing disk lists, accessing disk logs, etc.

Due to the powerful scalability of the FMZ platform, you can create your own quantized platform based on the extension API, allowing users to run disks on your platform etc.; see https://www.fmz.com/bbs-topic/1697

Become a Partner of FMZ

Promotion of the cloud classroom

The digital currency trading market is becoming increasingly focused on quantitative traders due to its specificity. In fact, programmatic trading is already the mainstream of digital currencies, and strategies such as hedging the market are always active in the market.www.fmz.comThis course is only $20 for beginners and has been helping thousands of beginners on the path to quantitative trading for more than four years.

PromotionNetEase Cloud classroom on digital currency quantification◎ Log in to NetEasy Cloud Classroom, share your course link (link with exclusive courseId), and other people register and buy the course through this link, you will get 50% of the total 10 yuan.

Promote the recruitment campaign

The consumer clicks on the promotion link and is recharged within six months of registration, and the commission is refunded in accordance with the valid amount in the valid order. The commission will be returned in the form of points to the promoter's account, and users can exchange inventors' quantitative trading platform account balance at a ratio of 10:1, or can later exchange inventors' quantitative surrounding goods with points.https://www.fmz.com/bbs-topic/3828

FMZ Quantitative Platform for the Enterprise

The entire FMZ website can be deployed on a dedicated server of an enterprise or a team for complete control and functionality customization. The FMZ website has been used and tested by about 100,000 users, achieving high availability and security, saving time costs for quantitative teams and enterprises. The enterprise version is aimed at small and medium-sized quantitative trading teams, commodity futures service providers, etc.

The City System

The specialized system that provides market liquidity and money management for exchanges is probably the most complete market making system on the market, and is used by many exchanges and teams.

Exchange program

The inventor's high-tech trading system uses memory capture technology, order processing speeds of up to 2 million pieces per second, which can ensure that order processing does not occur any delays and cartons. It can maintain the smooth and stable operation of exchanges with a number of online users of more than 20 million at the same time. Multi-layered, multi-cluster system architecture ensures the security of the system, stability, scalability.


More

bbbwwed2009Can you still join the group?

MAIKEOGod of grass and martial arts!

The grassWhat's up? // example of token futures push var ACCESSKEYID = 'Access key to your token account' var apiClient = Dial (('wss://api.hbdm.com/notification in to 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.toISOSstring (().substring ((0,19) is used to specify the date of the substring This is a list of all the different ways AccessKeyId='+ACCESSKEYID+'&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=' + encodeURIComponent ((Timestamp) is credited in the database. var signature = exchange.HMAC (("sha256", "base64", quest, "{{secretkey}}") What's up?

The Beat BrigadePlease tell me, if it is a websocket connecting to a subscription market, then it will get the market data using the read (()) function, right? If it is using exchange.GetTicker (()) it will not extract the data from the local cache, but will launch a rest request to return the data, right? Only one token supports changing the way the market is accepted via exchange.IO (websocket) and then using exchange.GetTicker (exchange.GetTicker) and exchange.GetDepth (exchange.GetDepth)) will no longer request data from the exchange rest, and will get data from the subscription market already received that exists in the local buffer. I get it, right?

The grassFollow the prompts

jsyzliuyuPlease verify that the FMZ Telegram group is now on the right.

Shaltiel ok

The grassYes, preferably all websockets with Dial, more intuitive control