[TOC] Antes de aprender este tutorial, você precisa aprender.Introdução à plataforma de quantificação do inventor FMZeEstratégias para escrever tutoriais iniciais para a plataforma de quantificação FMZA partir daí, a empresa começou a trabalhar com o seu próprio projeto.O tutorial inicial aborda as funções mais usadas, mas há muitas outras funções e funcionalidades que não foram apresentadas e que não serão cobertas neste tutorial.Após este tutorial, você será capaz de escrever estratégias mais livres e personalizadas. A plataforma FMZ é apenas uma ferramenta.
A plataforma FMZ é compatível com todas as exchanges suportadas e, para manter a uniformidade, o suporte à API de uma única exchange não é completo. Por exemplo, a captação de linhas K geralmente permite o número de linhas K ou o horário de início, enquanto a plataforma FMZ é fixa, algumas plataformas suportam pedidos em volume, a FMZ não suporta, etc. Portanto, é necessário um método para acessar diretamente os dados da exchange.Para interfaces abertas (como o mercado), pode ser usadoHttpQuery
Para adicionar informações sobre a conta, é necessário usar:IO
。Os parâmetros de entrada específicos também devem ser referidos na documentação API correspondente; o tutorial anterior foi apresentado.Info
O campo devolveu a informação original, mas ainda não resolveu o problema de não suportar a interface.
Retorna o conteúdo original (string) que foi retornado na última solicitação do REST API e pode ser usado para analisar a extensão da informação.
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)
}
Acesse a interface pública que os Js podem usarHttpQuery
O Python pode usar os pacotes relacionados, como:urllib
Ourequests
。
O HttpQuery é o método GET por padrão, mas também suporta mais funções, veja a documentação API.
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"))
Exemplos 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 as interfaces que exigem assinatura API-KEY, a função IO pode ser usada, e o usuário só precisa se preocupar com os parâmetros de transmissão, o processo de assinatura específico será feito pelo nível inferior.
A plataforma FMZ não suporta atualmente o BitMEX Stop Loss Order, mas é implementado através do IO, seguindo os seguintes passos:
https://www.bitmex.com/api/explorer/
。https://www.bitmex.com/api/v1/order
O método éPOST
O FMZ já tem um endereço de raiz especificado internamente, basta inserir "/api/v1/order".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")
//也可以直接传入对象
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 exchanges de moeda digital suportam o mercado de envio de websocket, e algumas exchanges suportam o websocket para atualizar as informações da conta. Em comparação com o rest API, o websocket geralmente tem um baixo atraso, alta frequência, não tem restrições de frequência do rest API da plataforma.
Este artigo abordará principalmente a plataforma de quantificação do inventor da FMZ, o uso da linguagem JavaScript, a conexão com a função Dial embalada na plataforma, especificações e parâmetros em documentos, pesquisa Dial, para implementar várias funções, a função Dial foi atualizada várias vezes, e este artigo abordará estratégias de gerenciamento de eventos baseadas em wss, bem como problemas de conexão de vários exchanges.
O que você pode fazer é fazer uma ligação direta para obter o ticker de segurança:
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr")
Para os dados de retorno que precisam ser comprimidos na conexão, compress especifica o formato de compressão, o modo representa o envio de dados de retorno que precisam ser comprimidos, como na conexão OKEX:
var client = Dial("wss://real.okex.com:10441/websocket?compress=true|compress=gzip_raw&mode=recv")
A função Dial suporta reconexão, feita pela linguagem Go subjacente, o que permite que as conexões detectadas sejam desligadas e reconectadas. É muito conveniente e recomendado para o conteúdo dos dados solicitados já estarem no url, como no exemplo de Binance.
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr|reconnect=true")
Subscrever mensagens do ws, algumas solicitações de trocas estão no url, e alguns canais exigem que você envie sua própria assinatura, como o Coinbase:
client = Dial("wss://ws-feed.pro.coinbase.com", 60)
client.write('{"type": "subscribe","product_ids": ["BTC-USD"],"channels": ["ticker","heartbeat"]}')
O websocket é geralmente usado para ler transações, mas também pode ser usado para obter pedidos e transferências de contas. O envio de dados criptografados, por vezes, pode ter grandes atrasos e deve ser usado com cautela. Como os métodos de criptografia são mais complexos, aqui estão alguns exemplos.
//火币期货推送例子
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)
}
}
O código é geralmente lido continuamente durante o ciclo de morte:
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数据
}
}
Os dados do wss são muito rápidos, o fundo do Go demora todos os dados na fila, e quando o programa chama read, ele retorna em seguida; enquanto as operações do disco físico podem causar um atraso, o que pode causar um acúmulo de dados. Para transações de transferência, transferência de contas, transferência de inserção de profundidade, etc., precisamos de dados históricos.
read()
Se não for adicionado nenhum parâmetro, retornará os dados mais antigos, bloqueando o retorno quando não houver dados.client.read(-2)
Retorna imediatamente os dados mais recentes, mas retorna null quando não há mais dados e precisa ser julgado novamente.
Dependendo de como tratar os dados antigos armazenados no cache, e se eles ficam bloqueados quando não há dados, o read tem diferentes parâmetros, como o gráfico abaixo, o que parece complicado, mas torna o programa mais flexível.
Para essa situação, o procedimento obviamente não pode usar o simples read (), pois uma troca bloqueia a mensagem pendente, em que a outra troca não receberá a mensagem, mesmo que haja uma nova mensagem.
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
}
}
Esta parte é mais problemática, pois o envio de dados pode ser interrompido, ou o tempo de envio é muito alto, mesmo que não seja possível receber o coração, o que não significa que os dados ainda estejam sendo enviados, pode ser definido um intervalo de eventos, se exceder o intervalo não receber uma atualização, a reconexão é reiniciada, e é melhor comparar o resultado do retorno de tempo e rest para ver se os dados são precisos.
Como os dados de push já foram usados, os programas também devem ser escritos como event drivers, cuidando de impulsionar os dados com frequência, sem que muitos pedidos causem fechamento.
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)
}
}
O modo de conexão do websocket, o modo de envio de dados, o conteúdo subscrito e o formato de dados de cada exchange são muitas vezes diferentes, de modo que a plataforma não é encapsulada e precisa de uma conexão automática com a função Dial.
PS. Algumas casas de câmbio, embora não ofereçam mercado de websocket, na verdade utilizam funções de modulação para fazer o login do site. Se você pesquisar, verá que o formato de assinatura e o formato de retorno são utilizados.
O JavaScript pode ser implementado em simultâneo através da função Go, e o Python pode usar uma biblioteca de vários fios correspondente.
Na implementação de estratégias de quantificação, em muitos casos, a execução em simultâneo pode reduzir a eficiência de aumento de atraso.
var depthA = exchanges[0].GetDepth()
var depthB = exchanges[1].GetDepth()
O requisito rest API tem um atraso, supondo que seja de 100 ms, então o tempo de aquisição de profundidade de duas vezes é realmente diferente, e o problema de atraso será mais proeminente se mais visitas forem necessárias, afetando a execução da política.
Como o JavaScript não tem muitos threads, a função Go foi encapsulada no fundo para resolver esse problema.GetDepth
,GetAccount
E assim por diante.IO
O que você está fazendo?exchange.Go("IO", "api", "POST", "/api/v1/contract_batchorder", "orders_data=" + JSON.stringify(orders))
Mas, devido ao mecanismo de design, a implementação é mais complicada.
var a = exchanges[0].Go("GetDepth")
var b = exchanges[1].Go("GetDepth")
var depthA = a.wait() //调用wait方法等待返回异步获取depth结果
var depthB = b.wait()
Na maioria dos casos simples, não há problema em escrever uma política assim. Mas note que cada ciclo de política tem que repetir o processo, e as variáveis intermediárias a e b são apenas auxiliares temporárias. Se temos muito trabalho em simultâneo, é necessário registrar a relação correspondente entre a e a profundidade A, b e a profundidade B. A situação é ainda mais complexa quando nossa tarefa de simultâneo não é determinada.
function G(t, ctx, f) {
return {run:function(){
f(t.wait(1000), ctx)
}}
}
Nós definimos uma função G, onde o parâmetro t é a função Go a ser executada, ctx é o contexto do programa de registro, f é a função com o valor específico.
Neste caso, o quadro de programação geral pode ser escrito como semelhante ao modelo de programação produtor-consumidor (com algumas diferenças), onde o produtor emite continuamente tarefas e o consumidor as executa em simultâneo. O código é apenas uma demonstração e não envolve a lógica de execução do programa.
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)
}
}
O que parece ser um ciclo de execução de uma função simples, na verdade, simplifica muito a complexidade do código, basta nos preocuparmos com as tarefas que o programa precisa produzir, e o worker () automaticamente as executa em simultâneo e retorna os resultados correspondentes. A flexibilidade é muito melhorada.
O tutorial de introdução ao gráfico inicial é uma biblioteca de gráficos recomendada, que pode atender às necessidades na maioria das vezes. Se precisar de mais personalização, pode operar diretamente com objetos Chart.
Chart({…})
Os parâmetros internos são HighStock e HighCharts objetos, apenas adicionando um parâmetro adicional__isStock
Para distinguir se é ou não HighStock. HighStock é mais focado em gráficos de sequência de tempo e, portanto, é mais usado. FMZ suporta basicamente os módulos básicos de HighCharts e HighStock, mas não suporta módulos adicionais.
Exemplos específicos de HighCharts:https://www.highcharts.com/demoO exemplo de HighStock:https://www.highcharts.com/stock/demoO código de referência desses exemplos pode ser facilmente transferido para o FMZ.
Pode ser chamado add (([series index ((como 0, dados]) para adicionar dados à série do índice especificado, chamada reset (() dados de gráfico vazios, reset pode ser usado com um parâmetro numérico, especificando o número reservado. Suporta exibição de vários gráficos, basta inserir parâmetros de conjunto quando configurado, como: var chart = Chart (([{...}, {...}, {...})), por exemplo, um gráfico tem duas séries, um gráfico tem uma série, um gráfico tem três séries, então quando se especifica uma série com 01, a série ID representa os dados das duas séries atualizadas do gráfico 1, quando se especifica uma série ID representa os dados dos dois séries do gráfico 1, quando se especifica a série ID indica os dados da primeira série do gráfico 2 e a série 3 indica os dados da primeira série do gráfico 3.
Um exemplo concreto:
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);
}
}
Um exemplo de uso de um layout gráfico:https://www.fmz.com/strategy/136056
O endereço específico do código aberto:https://github.com/fmzquant/backtest_python
Instalação
Introduza o seguinte comando na barra de comando:
pip install https://github.com/fmzquant/backtest_python/archive/master.zip
Exemplo simples
O parâmetro de retorno é definido como uma anotação no início do código da política, por exemplo, veja a interface de edição de políticas do site FMZ para salvar a configuração de retorno.
'''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
Revisão
Uma vez que a estratégia completa requer um ciclo morto, a EOF anormal será lançada para encerrar o processo após o retestamento, portanto, é necessário tolerar erros.
# !/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()
exchange.SetData ((arr), troca de fontes de dados de retorno, usando dados de linha K personalizados. O argumento arr, é um conjunto de elementos para dados de coluna de linha K (ou seja, um conjunto de dados de linha K, que apenas suporta retorno JavaScript temporariamente).
No arquivo arr, o formato de dados dos elementos individuais é:
[
1530460800, // time 时间戳
2841.5795, // open 开盘价
2845.6801, // high 最高价
2756.815, // low 最低价
2775.557, // close 收盘价
137035034 // volume 成交量
]
A fonte de dados pode ser importada para a biblioteca do tipo de modelo.
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("导入数据成功")
}
Observação: é necessário importar os dados personalizados (ou seja, os dados de configuração da função exchange.SetData) no momento da inicialização, e os ciclos de dados de linha K personalizados devem coincidir com os ciclos de linha K do nível inferior da configuração da página de retorno, ou seja: dados de linha K personalizados, com um tempo de linha K de 1 minuto, então os ciclos de linha K do nível inferior da configuração de retorno também devem ser definidos como 1 minuto.
Se o exchange não suportado e o exchange com API suportada forem exatamente iguais, mas apenas com um endereço base diferente, o endereço base pode ser suportado através de uma mudança de endereço base.
exchange.IO("base", "http://api.huobi.pro")
//http://api.huobi.pro为为支持交易所API基地址,注意不用添加/api/v3之类的,会自动补全
Nem todas as bolsas de câmbio suportam o FMZ, mas a plataforma oferece acesso ao protocolo geral.
Simplificando, o protocolo geral é o equivalente a um intermediário, que representa os pedidos dos administradores e retorna dados de acordo com os padrões correspondentes. O código do protocolo geral precisa ser completado por si mesmo, e escrever o protocolo geral representa que você pode acessar o exchange individualmente e concluir as políticas. O oficial FMZ às vezes publica uma versão exe do protocolo geral do exchange. O protocolo geral também pode ser concluído com Python, em que o protocolo pode ser executado como um disco físico normal no administrador.
Apresentação do acordo específico:https://www.fmz.com/bbs-topic/1052Exemplos de protocolo geral escrito em Python:https://www.fmz.com/strategy/101399
Assim como todas as operações das bolsas podem ser realizadas através de uma API, o site da FMZ também é baseado em uma API. Você pode solicitar sua própria implementação do API-KEY do site da FMZ, como criar, reiniciar, remover disco rígido, acessar listas de discos rígidos, acessar diários de discos rígidos e outras funções.
Graças à forte escalabilidade da plataforma FMZ, você pode criar sua própria plataforma de quantificação com base na API de extensão, para que os usuários executem discos físicos na sua plataforma.
O mercado de negociação de moedas digitais está cada vez mais sob a atenção de comerciantes quantificados devido às suas particularidades. Na verdade, a negociação programada já é o principal fluxo de moedas digitais, e estratégias como o hedge do mercado estão sempre fora do mercado.www.fmz.comO Currency Exchange (CCI) é a maior comunidade e plataforma de quantificação de moedas digitais e tem ajudado milhares de iniciantes no caminho da quantificação de transações por mais de quatro anos.
PromoçãoCurso de negociação quantitativa de moedas digitais em uma sala de aula em nuvemEntrar na aula em nuvem, compartilhar o seu link do curso (link com o curso id exclusivo), e outras pessoas se registrarem e comprarem o curso através deste link, você receberá 50% de 10 yuans de participação.
Os consumidores clicam no link de promoção e são recarregados no prazo de seis meses, e a nossa divisão reembolsa a comissão de acordo com a quantia válida no pedido válido. A comissão será devolvida em forma de pontos para a conta do promotor, o usuário pode trocar o inventor pelo saldo da conta da plataforma de negociação de quantificação em uma proporção de 10 a 1, ou pode trocar os inventores pelos produtos de quantificação de atividades específicas em uma data posterior.https://www.fmz.com/bbs-topic/3828
O site FMZ pode ser implementado em um servidor exclusivo de uma empresa ou equipe, permitindo o controle e a personalização completa das funções. O site FMZ foi usado e testado por cerca de 100.000 usuários, alcançando alta disponibilidade e segurança, economizando tempo e custos de equipe e empresa.
O sistema especializado em fornecer liquidez de mercado e gestão de fundos para as bolsas é provavelmente o sistema de negociação mais completo do mercado e é usado por muitas bolsas e equipes.
O sistema de negociação de tecnologia do inventor utiliza tecnologia de captura de memória, com uma velocidade de processamento de pedidos de até 2 milhões de páginas / segundo, o que garante que o processamento de pedidos não ocorra nenhum atraso e cartão. Pode manter o fluxo e o funcionamento estável de uma bolsa com mais de 20 milhões de usuários online ao mesmo tempo. A arquitetura do sistema com várias camadas e múltiplos clusters garante a segurança, a estabilidade e a escalabilidade do sistema. A implementação de funções, a atualização de versões sem interrupções é feita, garantindo o máximo da experiência de operação do usuário final.
BBBwwed2009Mas o que fazer?
O MAIKEODeus da erva, WWE!!!
Ervas daninhasNão. // exemplo de propulsão de futuros de tokens var ACCESSKEYID = 'chave de acesso da sua conta de tokens' var apiClient = Dial (('wss://api.hbdm.com/notificationdecompress=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}}") Não.
Brigada BeatPor favor, se o websocket está ligado ao mercado de assinatura, então você pode obter dados do mercado com a função read ((), certo? Se você usar o exchange.GetTicker ((), você não extrai dados do cache local, mas inicia um rest request para retornar dados, certo? Somente um token é suportado para alterar o modo como o mercado é aceito através do exchange.IO (WEB e depois com o exchange.GetTicker (WEB e exchange.GetDepth (WEB não solicitará dados do resto do mercado e receberá dados do mercado de assinaturas já recebidas no buffer local. Eu entendo, certo?
Ervas daninhasA partir da verificação da dica
JyzliuyuPor favor, verifique se a informação do grupo FMZ agora é errada.
ShaltielEstá bem.
Ervas daninhasSim, é melhor que todos os websockets usem o Dial, para um controle mais intuitivo.