FMZ est une plateforme de stratégie de mise au point de tutoriels

Auteur:Le foin, Créé: 2019-08-19 15:54:35, Mis à jour: 2021-06-08 16:14:57

Je suis désolé. Avant d'apprendre ce tutoriel, vous devez apprendre.Introduction à la plateforme de quantification des inventeurs FMZetStratégie de la plateforme de quantification FMZ pour écrire des didacticiels pour les débutantsIl est également très doué en langage de programmation.Le tutoriel de base traite des fonctions les plus couramment utilisées, mais il y a beaucoup d'autres fonctions et fonctionnalités qui ne sont pas présentées et qui ne seront pas couvertes dans ce tutoriel.Après avoir appris ce tutoriel, vous serez en mesure d'écrire des stratégies plus libres et plus personnalisées, la plateforme FMZ n'est qu'un outil.

Accéder aux données de base des bourses

La plate-forme FMZ est enveloppée dans tous les échanges pris en charge, et la prise en charge de l'API d'un seul échange n'est pas complète afin de maintenir l'uniformité. Par exemple, l'acquisition d'une ligne K permet généralement de transmettre le nombre de lignes K ou l'heure de début, alors que la plate-forme FMZ est fixe, certaines plateformes prennent en charge les commandes en vrac, FMZ ne le fait pas, etc. Il faut donc une méthode pour accéder directement aux données de l'échange.Pour les interfaces ouvertes (par exemple, le marché), vous pouvez utiliserHttpQuery, pour ajouter de l'information à votre compte, vous devez utiliserIOPour les paramètres de transmission spécifiques, veuillez vous référer à la documentation de l'API de l'échange.InfoLe champ retourne l'information originale, mais ne peut toujours pas résoudre le problème de la non-support de l'interface.

Je suis en train d' écrire.

Retourne le contenu original (une chaîne) de la dernière requête REST API, qui peut être utilisé pour analyser les informations d'extension par vous-même.

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 (()) accède à l'interface publique

Pour accéder à l'interface publique, Js peut être utiliséHttpQueryPython peut utiliser ses propres packages, tels que:urllibourequests

HttpQuery est la méthode GET par défaut, mais elle prend en charge plus de fonctionnalités, voir la documentation 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"))

Exemples de requêtes utilisées par Python

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

Fonction IO pour accéder à la porte de rapprochement

Pour les interfaces nécessitant une signature API-KEY, la fonction IO peut être utilisée, l'utilisateur n'a qu'à se soucier des paramètres de transmission, et le processus de signature spécifique est effectué par le sous-sol.

La plate-forme FMZ ne prend pas en charge les ordres de stop-loss BitMEX, mais utilise les étapes suivantes pour le faire.

  • Vous pouvez trouver la page d'explications de l'API de BitMEX:https://www.bitmex.com/api/explorer/
  • L'adresse suivante pour trouver BitMEX:https://www.bitmex.com/api/v1/orderLa méthode estPOSTComme FMZ a déjà spécifié l'adresse racine en interne, il suffit de saisir "/api/v1/order".
  • Paramètres correspondantssymbol=XBTUSD&side=Buy&orderQty=1&stopPx=4000&ordType=Stop

Le code spécifique:

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"}))

Plus d'exemples d'IO:https://www.fmz.com/bbs-topic/3683

Utiliser le websocket

Pratiquement tous les échanges de crypto-monnaie prennent en charge les transactions d'envoi de websockets, et certains d'entre eux prennent en charge les mises à jour des informations de compte websocket. Comparativement à l'API rest, le websocket a généralement un faible délai, une fréquence élevée et n'est pas limité par la fréquence de l'API rest de la plate-forme.

Cet article traite principalement de la plate-forme de quantification FMZ Inventor, utilise le langage JavaScript, utilise la fonction Dial enveloppée dans la plate-forme pour les connexions, les spécifications et les paramètres dans les documents, recherche Dial, pour réaliser diverses fonctionnalités, Dial a été mis à jour à plusieurs reprises, ce que cet article couvrira, et présente les stratégies basées sur les événements d'exécution, ainsi que les problèmes de connexion à plusieurs exchanges. Python peut également utiliser la fonction Dial, et une bibliothèque correspondante peut être utilisée.

1.websocket连接

Il est généralement possible de se connecter directement, par exemple pour obtenir un ticker de sécurité:

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

Pour les données renvoyées qui doivent être compressées, le format requis lors de la connexion est spécifié, le format requis lors de la compression est spécifié, le mode représentant l'envoi de données renvoyées qui doivent être compressées, comme pour la connexion OKEX:

var client = Dial("wss://real.okex.com:10441/websocket?compress=true|compress=gzip_raw&mode=recv")

La fonction Dial prend en charge la reconnexion, effectuée par le langage Go sous-jacent, la connexion détectée est déconnectée et reconnectée, ce qui est très pratique et recommandé pour le contenu des données demandées déjà dans l'url, comme l'exemple de Binance. Pour ceux qui ont besoin d'envoyer des messages, vous pouvez maintenir vous-même le mécanisme de reconnexion.

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

Il y a aussi des chaînes qui demandent de s'abonner elles-mêmes, comme Coinbase:

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

2.加密接口连接

Le websocket est généralement utilisé pour lire les transactions, mais il peut également être utilisé pour obtenir des commandes et des envois de comptes. Les envois de ces données cryptées peuvent parfois être longs et doivent être utilisés avec prudence.

    //火币期货推送例子
    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读取

En général, le code est lu en continu dans le cycle de la mort:

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数据
    }
}

Les données wss sont très rapides, la couche inférieure de Go met toutes les données en attente dans la file d'attente et les renvoie à la suite de l'appel de programme read; tandis que les opérations de téléchargement sur le disque réel entraînent des retards, ce qui peut entraîner une accumulation de données. Pour les informations telles que le transfert de transaction, le transfert de compte, le transfert de profondeur, etc., nous avons besoin de données historiques, pour les données de marché, nous nous intéressons principalement aux données les plus récentes et non aux données historiques.

read()Si aucun paramètre n'est ajouté, il renvoie les données les plus anciennes et bloque le retour lorsque les données ne sont pas disponibles.client.read(-2)Les données sont immédiatement renvoyées à la dernière date, mais elles retournent à zéro lorsqu'il n'y a plus de données, ce qui nécessite une nouvelle référence.

En fonction de la façon dont l'on traite les anciennes données mises en cache, et de l'absence de blocage, les paramètres de lecture varient, comme le montre la figure ci-dessous, ce qui semble compliqué mais rend le programme plus flexible.img

4.连接多个交易所websocket

Dans ce cas, il est évident que la procédure ne peut pas utiliser un simple read (), car un échange peut bloquer les messages en attente, ce qui empêche l'autre échange de recevoir les nouveaux messages.

    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.断线重连问题

Cette partie est plus gênante, car les données peuvent être interrompues, ou le délai de push est extrêmement élevé, même si la réception du heartbeat ne signifie pas que les données sont toujours en cours de push, un intervalle d'événement peut être défini, une reconnexion est effectuée si aucune mise à jour n'est reçue au-delà de cet intervalle, et il est préférable de comparer les résultats de retour avec rest pour voir si les données sont exactes.

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

Étant donné que les données de boost sont déjà utilisées, le programme doit naturellement être écrit comme un pilote d'événements, en prenant soin de booster les données fréquemment, sans trop de requêtes entraînant une fermeture.

    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.总结

La connectivité des websockets des différents échanges, la manière dont les données sont envoyées, le contenu à souscrire et le format des données sont souvent différents, de sorte que la plate-forme n'est pas enveloppée et nécessite une connexion automatique à l'aide de la fonction Dial. Cet article couvre essentiellement quelques précautions de base.

PS. Certains échanges, bien qu'ils n'offrent pas de marché de websocket, utilisent en fait une fonctionnalité de mise en mode pour accéder à leur site Web.

Synchronisation de plusieurs fils

JavaScript peut être réalisé en parallèle avec la fonction Go, Python peut utiliser une bibliothèque multi-threads correspondante.

Dans de nombreux cas, l'exécution en parallèle peut réduire l'efficacité de l'amélioration de la latence lors de la mise en œuvre d'une stratégie de quantification.

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

Une fois que la requête rest API est retardée, supposons que ce soit 100 ms, alors les deux temps d'acquisition de profondeur sont en fait différents. Si plus d'accès sont nécessaires, le problème de retard sera plus important et affectera l'exécution de la stratégie.

Comme JavaScript n'a pas beaucoup de threads, la fonction Go est enveloppée en bas pour résoudre ce problème.GetDepth,GetAccountJe suis d'accord avec toi.IOIl y a aussi des appels comme:exchange.Go("IO", "api", "POST", "/api/v1/contract_batchorder", "orders_data=" + JSON.stringify(orders))Mais, en raison de la mécanique de conception, la mise en œuvre est plus compliquée.

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

Dans la plupart des cas simples, il n'y a pas de problème à écrire des stratégies ainsi. Mais notez que chaque cycle de stratégie doit répéter ce processus, les variables intermédiaires a et b étant en fait des auxiliaires temporaires. Si nous avons beaucoup de tâches simultanées, nous devons enregistrer une correspondance entre a et depthA, b et depthB, ce qui est plus compliqué lorsque notre tâche simultanée n'est pas certaine.

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

Nous définissons une fonction G, dont les paramètres t sont la fonction Go à exécuter, ctx est le contexte du programme d'enregistrement, et f est la fonction à l'attribution spécifique.

Dans ce cas, l'ensemble du cadre de programmation peut être écrit de manière similaire au modèle de programmation producteur-consommateur (avec quelques différences), dans lequel le producteur continue à émettre des tâches et les consommateurs les exécutent en même temps, le code étant une simple démonstration et n'impliquant pas la logique d'exécution du programme.

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)
    }
}

Ce qui semble être une simple fonctionnalité réalisée en un clin d'œil simplifie considérablement la complexité du code, nous ne nous soucions que de savoir quelles tâches le programme doit produire, et le programme worker () les exécute automatiquement en parallèle et renvoie les résultats correspondants.

Graphique de la fonction Chart

Les tutoriels de débutants sur les diagrammes sont une bibliothèque de diagrammes recommandée, qui peut répondre aux besoins dans la plupart des cas. Si vous avez besoin de plus de personnalisation, vous pouvez utiliser directement les objets Chart.

Chart({…})Les paramètres internes sont les objets HighStock et HighCharts, juste un paramètre supplémentaire ajouté__isStockPour distinguer si c'est un HighStock. HighStock est plus axé sur les graphiques de séquence chronologique et est donc plus souvent utilisé. FMZ prend en charge les modules de base de HighCharts et HighStock, mais ne prend pas en charge les modules supplémentaires.

Des exemples spécifiques de HighCharts:https://www.highcharts.com/demoLe site de HighStock est un exemple:https://www.highcharts.com/stock/demoLe code de ces exemples peut être facilement porté sur FMZ.

On peut appeler add (([series index ((par exemple 0, données]) pour ajouter des données à la série de l'index spécifié, appeler reset (() pour vider les données du graphique, reset peut être un paramètre numérique pour spécifier le nombre de lignes réservées. Supporte l'affichage de plusieurs graphiques, la configuration nécessite simplement de transmettre des paramètres de gamme, par exemple: var chart = Chart (([{...}, {...}, {...})), par exemple, le graphique 1 a deux séries, le graphique a une série, le graphique a trois séries, alors add avec une séquence 01 représente les données des deux séries du graphique 1.

L'exemple le plus concret:

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);
    }
}

Voici un exemple d'utilisation d'une mise en page graphique:https://www.fmz.com/strategy/136056

Résumé de l'étude

Retour en Python

L'adresse de l'outil est:https://github.com/fmzquant/backtest_python

Installation

Dans la ligne de commande, entrez la commande suivante:

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

Un exemple simple

Les paramètres de retouche sont définis sous forme d'annotations au début du code de la stratégie, voir plus précisément l'interface d'édition de la stratégie du site FMZ pour enregistrer les paramètres de retouche.

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

Réécriture

Étant donné que la stratégie complète nécessite un cycle mort, il est nécessaire de faire preuve de tolérance à l'erreur pour éliminer les anomalies de l'EOF à la fin du retest.

# !/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()         

Données de retouche personnalisées

exchange.SetData ((arr), basé sur le paramètre arr, est un élément d'un ensemble de données de colonne de ligne K (c'est-à-dire un ensemble de données de ligne K, qui ne prend temporairement en charge que le retouche JavaScript).

Le format de données d'un élément unique d'une matrice arr est:

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

Les sources de données peuvent être importées dans une bibliothèque de types de modèles.

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("导入数据成功")
}

Remarque: il est impératif que les données personnalisées (c'est-à-dire les données de configuration de la fonction exchange.SetData) soient importées en premier lors de l'initialisation. Les cycles de données de ligne K personnalisées doivent être en accord avec les cycles de ligne K du sous-ensemble des paramètres de la page de réglage, c'est-à-dire que si les données de ligne K personnalisées ont une durée de ligne K de 1 minute, les cycles de ligne K du sous-ensemble des paramètres de réglage sont également définis à 1 minute.

Utilisez les échanges non pris en charge par FMZ

Si l'API de l'échange non pris en charge est exactement la même que celle de l'échange pris en charge, mais que l'adresse de base est différente, elle peut être prise en charge en changeant l'adresse de base.

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

Toutes les plateformes ne sont pas compatibles avec FMZ, mais la plateforme offre un accès au protocole général.

  • Le programme crée un service en ligne en écrivant son propre code pour accéder à l'échange.
  • Ajouter des échanges sur la plateforme FMZ, spécifier l'adresse et le port du service réseau.
  • Lorsque l'administrateur exécute le protocole général sur le disque de l'échange, les accès à l'API de la stratégie sont envoyés au protocole général.
  • Le protocole général accède à l'échange sur demande et renvoie les résultats au dépositaire.

En termes simples, le protocole général est l'équivalent d'un intermédiaire, qui exécute les demandes des hôtes et renvoie les données selon les normes appropriées. Le code du protocole général doit être complété par lui-même, l'écriture du protocole général représente en fait que vous pouvez accéder à l'échange individuellement et terminer la politique.

L'accord est présenté comme suit:https://www.fmz.com/bbs-topic/1052Voici quelques exemples de protocoles génériques en Python:https://www.fmz.com/strategy/101399

Créer sa propre plateforme de quantification

Tout comme les opérations d'une bourse, le site FMZ est également basé sur l'API. Vous pouvez demander votre propre application API-KEY pour créer, redémarrer, supprimer un disque dur, accéder à une liste de disques dur, accéder à des journaux de disques dur, etc.

Grâce à la grande extensibilité de la plateforme FMZ, vous pouvez créer votre propre plateforme quantitative basée sur l'API d'extension, permettre aux utilisateurs d'exécuter des disques virtuels sur votre plateforme, etc.; voir https://www.fmz.com/bbs-topic/1697

Devenez partenaire de FMZ

Les classes en nuage en ligne

Le marché des transactions de crypto-monnaie est de plus en plus attiré par les traders quantifiés en raison de ses particularités. En fait, les transactions programmatiques sont déjà le courant dominant de la crypto-monnaie, et des stratégies telles que le hedging et le marché ne sont pas toujours actives sur le marché.www.fmz.comLe cours, qui coûte seulement 20 dollars, est destiné aux débutants.

La diffusionCours de négociation quantitative de la monnaie numérique dans le cloud en ligneVous pourrez également vous inscrire sur le site de l'établissement et partager le lien de votre cours (le lien est accompagné d'un ID de cours exclusif), et si d'autres personnes s'inscrivent et achètent des cours via ce lien, vous obtiendrez une part de 50% de 10 yuans au total.

Promotion de la remise en service

Les consommateurs cliquent sur le lien promotionnel et, au bout de six mois après leur inscription, ils sont rechargés. La commission est remboursée en fonction du montant effectif de la commande valide. La commission est remboursée sous forme de points au compte du promoteur. Les utilisateurs peuvent échanger le solde du compte de la plate-forme de négociation quantitative de l'inventeur à un taux de 10:1 ou échanger les produits environnants de l'inventeur avec des points plus tard.https://www.fmz.com/bbs-topic/3828

FMZ est une plateforme de quantification

L'intégralité du site FMZ peut être déployée sur le serveur exclusif d'une entreprise ou d'une équipe, pour un contrôle et une fonctionnalité entièrement personnalisés. Le site FMZ a été utilisé et testé par environ 100 000 utilisateurs, avec une grande disponibilité et sécurité, ce qui permet d'économiser du temps et des coûts pour les équipes de quantification et les entreprises.

Le système de la ville

Le système professionnel de liquidité et de gestion des fonds pour les bourses est probablement le système de négociation le plus complet sur le marché et est utilisé par de nombreuses bourses et équipes.

Le programme des bourses

Le système de trading technologique de l'inventeur utilise une technologie de prise de vue en mémoire, une vitesse de traitement des commandes allant jusqu'à 2 millions de pièces par seconde, ce qui garantit que le traitement des commandes ne sera pas retardé et ne sera pas cassé. Il peut maintenir le fonctionnement fluide et stable d'un échange avec plus de 20 millions d'utilisateurs en ligne en même temps. L'architecture du système à plusieurs niveaux et à plusieurs clusters garantit la sécurité, la stabilité et l'évolutivité du système. Le déploiement de fonctionnalités, les mises à jour de versions sont effectuées sans interruption maximale, garantissant l'expérience d'utilisation des utilisateurs finaux.


Plus de

Il est mort en 2009.Vous pouvez toujours rejoindre le groupe?

- Je vous en prie.Dieu de l'herbe et de la guerre!

Le foinJe ne sais pas. // exemple de mise en place de jetons à terme var ACCESSKEYID = 'Clé d'accès à votre compte de jetons' Var apiClient = Dial (('wss://api.hbdm.com/notification dans le compress=gzip&mode=recv') Var date = nouvelle date (); var now_utc = Date.UTC ((date.getUTCFullYear ((), date.getUTCMonth ((), date.getUTCDate ((), date.getUTCHours ((), date.getUTCMinutes ((), date.getUTCSeconds (())); Var utc_date = nouvelle date (actuellement utc) Var Timestamp = utc_date.toISOSstring (().substring ((0,19) est un fichier de fichiers sous-jacent au fichier de fichiers sous-jacent. Var quest = 'GET\napi.hbdm.com\n/notification\n'+'AccessKeyId='+ACCESSKEYID+'&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=' + encodeURIComponent (en anglais seulement) Var signature = exchange.HMAC (("sha256", "base64", quest, "{{secretkey}}") Je ne sais pas.

L'armée américaineS'il vous plaît, si le websocket est connecté à un marché d'abonnement, il obtient les données du marché avec la fonction read ((), n'est-ce pas? Si vous utilisez exchange.GetTicker ((), il ne récupère pas les données du cache local, mais lance une requête rest pour les retourner, n'est-ce pas? Seule une famille de jetons prend en charge la modification de l'acceptation des marchés par l'intermédiaire de l'exchange.IO ("websocket") et utilise ensuite l'exchange.GetTicker et l'exchange.GetDepth (), qui ne demandent plus de données à l'échange rest, mais obtiennent des données dans les marchés de souscription déjà reçus présents dans le coussin local. Je comprends, n'est ce pas?

Le foinSuivez la vérification des indices

Jyzliuyu estS'il vous plaît vérifier que le groupe FMZ est là.

ShaltielÇa va.

Le foinOui, il est préférable que tous les websockets utilisent Dial, plus intuitif.