[TOC]
Antes de aprender este tutorial, você precisa estudarComece com a plataforma FMZ QuanteTutorial básico para a plataforma FMZ Quant, e tornar-se proficiente em linguagens de programação.O tutorial elementar abrange as funções mais usadas, mas há muitas funções e recursos que não foram introduzidos, e eles não serão abordados neste tutorial.Depois de aprender este tutorial, você será capaz de escrever mais estratégias gratuitas e personalizadas, e a plataforma FMZ Quant é apenas uma ferramenta.
A plataforma FMZ Quant encapsula todas as plataformas suportadas. A fim de manter a uniformidade, nosso suporte para uma única API de plataforma ainda não está completo. Por exemplo, o GetRecords pode passar o número de linhas K ou o horário de início, enquanto é fixado na plataforma FMZ; algumas plataformas suportam encomenda por lotes, enquanto a FMZ não suporta isso, e assim por diante.Para interfaces públicas (como cotações de mercado), pode utilizarHttpQuery
, e para interfaces criptografadas (que envolvam informações da conta), é necessário utilizarIO
.Para parâmetros de entrada específicos, consulte o documento API da plataforma correspondente.Info
campo retorna informações brutas, mas ainda não faz diferença sobre o problema de não suportar interfaces.
Ele retorna o conteúdo bruto (correntes) solicitado pela última API REST, que pode ser usado para analisar as informações estendidas por si só.
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)
}
Para acessar interfaces públicas, Js pode usarHttpQuery
, e Python pode usar pacotes relacionados, tais comourllib
ourequests
.
HttpQuery é padrão para o método GET, e suporta mais funções; consulte o documento API para mais detalhes.
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"))
Exemplo de Python usando solicitações:
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()
Para interfaces que exigem assinaturas API-KEY, a função IO pode ser usada, e os usuários só precisam se preocupar com os parâmetros de entrada, e o processo de assinatura específico será concluído pela camada subjacente.
A plataforma FMZ atualmente não suporta ordens de stop-loss do BitMEX, que podem ser implementadas através do IO, de acordo com as seguintes etapas:
https://www.bitmex.com/api/explorer/
;https://www.bitmex.com/api/v1/order
, com o método dePOST
; para FMZ já especificou internamente o endereço de base, você só precisa passar em symbol=XBTUSD&side=Buy&orderQty=1&stopPx=4000&ordType=Stop
.Código específico:
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"}))
Mais exemplos de IO:https://www.fmz.com/bbs-topic/3683
Basicamente, todas as plataformas de criptomoedas suportam websocket para enviar cotações de mercado, e algumas plataformas suportam websocket para atualizar informações de conta. Comparado com o rest API, o websocket geralmente tem as vantagens, como baixa latência, alta frequência e não ser limitado pela frequência do rest API da plataforma, etc. A desvantagem é que há um problema de interrupção, cujo processamento não é intuitivo.
Este artigo irá principalmente introduzir como usar a linguagem JavaScript e como usar a função Dial encapsulada pela plataforma para se conectar, na plataforma FMZ Quant; para instruções específicas e parâmetros estão no documento, você pode procurar por Dial; para realizar várias funções, a função Dial foi atualizada várias vezes.
Geralmente, conecte-se diretamente pelo Websocket; por exemplo, para obter Binance tricker push:
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr")
Se os dados retornados estiverem em formato comprimido, deve ser feita uma especificação ao ligar;
var client = Dial("wss://real.okex.com:10441/websocket?compress=true|compress=gzip_raw&mode=recv")
A função Dial suporta a reconexão, que é feita pelo Golang subjacente. Se a conexão detectada falhar, ela será reconectada. Para os dados de solicitação já na url, como o exemplo do Binance agora, é muito conveniente e recomendado. Para aqueles que precisam enviar mensagens de assinatura, eles podem manter o mecanismo de reconexão sozinhos.
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr|reconnect=true")
Para se inscrever em mensagens wss, algumas solicitações da plataforma estão no url, e algumas precisam enviar os próprios canais assinados, como o coinbase:
client = Dial("wss://ws-feed.pro.coinbase.com", 60)
client.write('{"type": "subscribe","product_ids": ["BTC-USD"],"channels": ["ticker","heartbeat"]}')
Geralmente, o websocket é usado para ler cotações de mercado, mas também pode ser usado para obter ordens e push de conta. O push de tais dados criptografados às vezes tem um longo atraso e deve ser usado com cautela. Como o método de criptografia é mais complicado, aqui estão alguns exemplos dados para referência. Observe que apenas o AccessKey é necessário, que pode ser definido como um parâmetro de estratégia. Se o SecretKey for necessário, ele pode ser chamado implicitamente pela função exchange.HMAC(() para garantir a segurança.
//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)
}
}
Geralmente, pode ser lido continuamente em um loop infinito.
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
}
}
A velocidade de transferência de dados do wss é muito rápida. A camada inferior do Golang armazenará em cache todos os dados na fila e, quando o programa chamar, os dados serão devolvidos. No entanto, operações como colocar uma ordem no bot causarão atrasos, o que pode resultar no acúmulo de dados. Para informações como transferência de execução de negociação, transferência de conta e transferência de interpolação de profundidade, precisamos dos dados do histórico. Para os dados do mercado de cotações, na maioria dos casos, nos preocupamos apenas com os dados mais recentes, não com os dados do histórico.
Seread()
adiciona nenhum parâmetro, ele vai retornar os dados mais antigos, e bloquear até retornar quando não há dados.client.read(-2)
para devolver os dados mais recentes imediatamente, mas quando não existem dados, ele devolverá nulo, que precisa ser julgado antes da referência.
Dependendo de como lidar com os dados cacheados antigos e se ele é bloqueado quando não há dados,
Neste caso, é óbvio que simplesmente usar
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
}
}
Esta parte do processamento é mais problemática, porque os dados de push podem ser interrompidos, ou o atraso de push é extremamente longo. Mesmo que o batimento cardíaco possa ser recebido, isso não significa que os dados ainda estejam sendo empurrados. Você pode definir um intervalo de evento; se nenhuma atualização for recebida após o intervalo, reconecte; é melhor comparar os resultados retornados por
Para os dados de push foram usados, o programa será naturalmente escrito como evento desencadeado; prestar atenção à frequência de dados de push, porque os pedidos de alta frequência levarão a ser bloqueado; geralmente você pode escrever:
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)
}
}
O método de conexão, o método de transmissão de dados, o conteúdo assinado e o formato de dados do websocket em cada plataforma são muitas vezes diferentes, então a plataforma não o encapsula e precisa usar a função Dial para se conectar sozinha.
PS: Embora algumas plataformas não forneçam citações de websocket, na verdade, quando você entra no site para usar a função de depuração, você descobrirá que todos eles estão usando o websocket push.
O JavaScript pode realizar a concorrência pela função Go, e o Python pode usar a biblioteca de multithreads correspondente.
Durante a realização de estratégias quantitativas, a execução simultânea pode reduzir o atraso de tempo e melhorar a eficiência.
var depthA = exchanges[0].GetDepth()
var depthB = exchanges[1].GetDepth()
Quando uma solicitação de API de repouso é atrasada, por exemplo, o tempo de atraso é de 100 milissegundos, então o tempo para obter a profundidade duas vezes é realmente diferente; se mais acessos forem necessários, os problemas de atraso serão mais óbvios, o que afetará a execução da estratégia.
Como o JavaScript não tem multithread, a camada subjacente encapsula a função Go para resolver este problema.GetDepth
, GetAccount
e assim por diante.IO
é também apoiada, como:exchange.Go("IO", "api", "POST", "/api/v1/contract_batchorder", "orders_data=" + JSON.stringify(orders))
, mas devido ao mecanismo de concepção, é mais tedioso de implementar.
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()
Em casos mais simples, escrever estratégias dessa maneira é bom. Mas note que o processo é repetido toda vez que o loop da estratégia, e as variáveis intermediárias a e b são na verdade apenas temporariamente auxiliares. Se temos muitas tarefas simultâneas, precisamos registrar adicionalmente a correspondência entre a e depthA, b e depthB. Quando nossas tarefas simultâneas são incertas, a situação é mais complicada. Portanto, esperamos realizar uma função: ao escrever a função Go simultaneamente, vincular uma variável ao mesmo tempo; quando o resultado de execução simultânea retorna, o valor do resultado é automaticamente atribuído à variável, eliminando assim a necessidade de fazer com que as variáveis intermediárias e o programa sejam mais concisos. A implementação específica é a seguinte:
function G(t, ctx, f) {
return {run:function(){
f(t.wait(1000), ctx)
}}
}
Nós definimos uma função G, onde o parâmetro
Neste momento, a estrutura geral do programa pode ser escrita como um modelo, semelhante ao modelo
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)
}
}
Parece que apenas uma função simples foi implementada nas operações acima. Na verdade, isso simplificou muito a complexidade do código. Nós só precisamos nos preocupar com quais tarefas o programa precisa gerar, e o programa
No tutorial elementar, a biblioteca de classes de desenho é recomendada na introdução do desenho, que pode atender às necessidades na maioria dos casos.
Os parâmetros internos deChart({…})
são os objetos de HighStock e HighCharts, mas um parâmetro adicional__isStock
O FMZ suporta basicamente os módulos básicos de HighCharts e HighStock, mas não suporta módulos adicionais.
O exemplo específico de HighCharts:https://www.highcharts.com/demoExemplo de HighStock:https://www.highcharts.com/stock/demoVocê pode consultar os códigos nesses exemplos, e transplantar-los para FMZ convenientemente.
Você pode chamar adicionar ([indice de série ((como 0), dados]) para adicionar dados à série com o índice especificado. Chamar reset() para limpar os dados do gráfico; reset pode tomar um parâmetro de número e especificar a quantidade a ser salva. A exibição de gráficos múltiplos é suportada, que só precisa passar nos parâmetros da matriz durante a configuração, como: var chart = Chart([{...}, {...}, {...}]). Por exemplo, se Chart1 tem duas séries, Chart2 tem uma série, e Chart3 tem uma série, ao chamar adicionar, os IDs das séries 0 e 1 são especificados para representar separadamente os dados das duas séries no gráfico atualizado1; a série 2 é especificada para representar os dados da primeira série no gráfico ID2; a série 3 é especificada para representar o primeiro dos dados da série no gráfico 3.
Um exemplo específico:
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);
}
}
Exemplo de utilização do layout do gráfico:https://www.fmz.com/strategy/136056
Endereço específico de código aberto:https://github.com/fmzquant/backtest_python
Instalação
Introduza o seguinte comando na linha de comando:
pip install https://github.com/fmzquant/backtest_python/archive/master.zip
Um exemplo simples
Configure os parâmetros do backtest no início do código de estratégia sob a forma de observação, e os detalhes serão exibidos no botão
'''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
Teste de retrocesso
Para estratégias completas precisam de loops infinitos, o erro EOF será levantado após o backtest terminar; portanto, devemos fazer a tolerância a falhas bem.
# !/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) muda a fonte de dados de backtest e usa dados de linha K personalizados. O parâmetro
no conjunto arr, o formato de dados de um único elemento:
[
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
]
A fonte de dados pode ser importada no
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")
}
Observação: certifique-se de importar os dados personalizados primeiro (ou seja, chamar a função exchange.SetData para definir os dados) durante a inicialização. O período de dados de linha K personalizado deve ser consistente com o período de linha K da camada subjacente definido na página do backtest, ou seja: dados de linha K personalizados ; o tempo de uma linha K é de 1 minuto, portanto, o período da linha K da camada subjacente definida no backtest também deve ser definido em 1 minuto.
Se a API de uma plataforma não suportada é exatamente a mesma que a de uma plataforma suportada, exceto o endereço de base, a plataforma não suportada pode ser suportada mudando o endereço de base. Para ser específico, selecione uma plataforma suportada ao adicionar uma plataforma, mas preencha a API-KEY da plataforma não suportada e use IO para mudar o endereço de base na estratégia, como:
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
Nem todas as plataformas são suportadas pela FMZ, mas nossa plataforma forneceu o método de acesso do protocolo geral.
Em termos simples, o protocolo geral é como um intermediário, proxyando a solicitação do docker e devolvendo os dados, de acordo com o padrão correspondente. O código do protocolo geral precisa ser preenchido por si mesmo. Escrever o protocolo geral realmente significa que você pode acessar a plataforma exclusivamente e concluir a estratégia. FMZ oficial às vezes libera a versão exe do protocolo geral das plataformas. O protocolo geral também pode ser feito em Python, que pode então ser executado no docker como um bot comum.
Introdução específica do protocolo geral:https://www.fmz.com/bbs-topic/9120Exemplo de escrita de protocolo geral em Python:https://www.fmz.com/strategy/101399
Assim como várias operações de uma plataforma podem ser implementadas através da API, o site da FMZ também é baseado na API. Você pode solicitar sua própria API-KEY do site da FMZ para realizar funções, como
Devido à poderosa extensão da plataforma FMZ Quant, você pode criar sua própria plataforma quantitativa baseada na extensão API, permitindo que seus usuários executem bots em sua plataforma, etc. Referência específica:https://www.fmz.com/bbs-topic/1697 .
O mercado de negociação de criptomoedas tem atraído cada vez mais a atenção dos traders quantitativos devido à sua particularidade. Na verdade, a negociação de programas tornou-se a corrente principal da criptomoeda, e estratégias como hedging e market making estão sempre ativas no mercado.www.fmz.com) atualmente a maior comunidade e plataforma quantitativa de criptomoedas, tem ajudado milhares de iniciantes no caminho para a negociação quantitativa por mais de 4 anos.
Promoção daCurso de negociação quantitativa de criptomoedas no NetEase Cloud Classroom. Entre no NetEase Cloud Classroom e compartilhe seu link do curso (o link tem um curso único). Outros, que se registrarem e comprarem o curso através deste link, trarão 50% do total como comissão, ou seja, 10 yuans. Siga a conta pública do WeChat da
Os consumidores que clicar no link da promoção, se registrar e recarregar dentro de meio ano, desfrutarão da política de que nossa empresa reembolsará de acordo com o valor efetivo na ordem válida. A comissão será devolvida à conta do promotor na forma de pontos. Os usuários podem trocar os pontos para o saldo da conta da plataforma FMZ em uma proporção de 10: 1, e os usuários também podem usar os pontos para trocar os produtos relacionados da FMZ Quant no futuro.https://www.fmz.com/bbs-topic/3828
O site completo da FMZ pode ser implantado no servidor exclusivo de uma empresa ou equipe para controle completo e personalização funcional. O site da FMZ foi usado e testado por cerca de 100.000 usuários e alcançou alta disponibilidade e segurança, o que pode economizar tempo para equipes quantitativas e empresas. A versão empresarial é para equipes de negociação quantitativa de médio porte, provedores de serviços de futuros de commodities, etc. Entre em contato com o administrador para citações específicas.
O sistema profissional, que fornece a liquidez do mercado e a gestão de fundos para as plataformas, pode ser o sistema de criação de mercado mais melhorado do mercado.
O sistema de negociação de tecnologia FMZ adota tecnologia de correspondência de memória, e a velocidade de processamento de pedidos é tão alta quanto 2 milhões de transações por segundo, o que pode garantir que não haverá atraso ou atraso no processamento de pedidos. Pode manter a operação suave e estável de plataformas com mais de 20 milhões de usuários online simultâneos. A estrutura do sistema de múltiplas camadas e multiclusters garante a segurança, estabilidade e extensão do sistema. A implantação de funções e atualizações de versões podem ser realizadas sem tempo de inatividade, o que garante o máximo da experiência operacional dos usuários do terminal.