Il s'agit d'un modèle de marché WebSocket officiellement développé par FMZ, qui peut être copié et sauvegardé comme modèle en cliquant sur ce modèle dans la nouvelle stratégie:https://www.fmz.com/strategy/470349
La stratégie actuelle de FMZ est principalement l'emballage des API REST traditionnelles, qui permettent d'établir une connexion réseau à chaque étape d'accès à l'API pour obtenir des données de marché par consultation. Cette méthode est simple et facile à utiliser et suffit parfaitement pour la plupart des besoins.
Cependant, le protocole REST présente des problèmes de retard inhérents, qui sont amplifiés lorsque plusieurs paires de transactions ou plusieurs stratégies d'échange sont nécessaires. Bien que la fonction Go de la plate-forme puisse être exécutée en parallèle, le problème de retard persiste, car il est difficile de répondre aux besoins de transactions stratégiques à une fréquence relativement élevée, et les transactions sont trop nombreuses, la fréquence de consultation est trop rapide et la fréquence d'accès à la plate-forme est limitée.
Les échanges actuels sont également très lourds sur le serveur, offrant des protocoles WebSocket parfaits et recommandés aux utilisateurs d'API. Par rapport au protocole REST, le WebSocket offre une connexion bidirectionnelle durable qui permet aux échanges de pousser des données vers le client en temps réel, en évitant des demandes et des réponses fréquentes, ce qui réduit considérablement les retards. En général, si le délai d'accès à l'API REST est d'environ 20 ms, le WebSocket a probablement un retard de 2 ms.
La plate-forme de trading quantitative FMZ a été très tôt compatible avec le protocole WebSocket et est relativement facile à appeler, mais pour les utilisateurs débutants, il est trop compliqué de traiter plusieurs abonnements, de souscrire à plusieurs marchés d'échanges et d'être intégré efficacement et facilement dans l'ensemble du processus stratégique. Le modèle public WebSocket pour l'accélération des données de marché en temps réel, qui est très facile à utiliser, est entièrement compatible avec les appels d'API actuellement enveloppés. Pour la plupart des stratégies REST originales, il suffit de modifier directement l'utilisation et d'accélérer votre stratégie.
Les principales caractéristiques:
Notez que cette stratégie utilise TypeScript, ce qui peut sembler un peu étrange si vous n'êtes qu'un habitué de JavaScript. TypeScript introduit un système de types et des fonctionnalités linguistiques plus riches basées sur JavaScript, pour des applications qui nécessitent un traitement de la logique complexe comme les transactions quantitatives.
En outre, la stratégie utilise le mécanisme d'asynchronisation de la plate-forme FMZ, où les fils peuvent envoyer des messages au fil principal via la fonction __threadPostMessage. Cette méthode est asynchrone et s'applique aux fils principaux pour les notifications de mise à jour des données générées dans les fils. Les données peuvent être partagées entre le fil principal et les fils principaux via les fonctions __threadGetData et __threadSetData. Cette méthode permet aux fils d'accéder et de modifier l'état de partage.
Le principe principal de cette stratégie est de connecter les échanges de devises numériques traditionnelles via WebSocket pour recevoir des données de marché en temps réel (comme des informations de profondeur et des informations de transaction) afin de fournir un support de données pour des décisions de transaction quantitatives.
1. Configuration des connexions WebSocket
setupWebsocket
Une fonction est utilisée pour initialement connecter un WebSocket et recevoir des données de marché. Elle reçoit un paramètremain_exchanges
Les échanges en ligne sont des échanges en ligne, ce qui signifie que vous devez être connecté.
MyDial
Les fonctions: crée une connexion WebSocket, enregistre le temps de connexion, et exécute le temps de fermeture lorsque la connexion est fermée.updateSymbols
Les fonctions• Vérifiez régulièrement s'il y a de nouvelles demandes d'abonnement et mettez à jour la liste des transactions en cours si nécessaire.2. Traitement des données
supports
Les objets définissent les échanges pris en charge et leurs fonctions de traitement (par exemple:Binance
Les fonctions de traitement de chaque échange sont chargées d'analyser les messages reçus et d'extraire les données pertinentes.
processMsg
Les fonctionsLes informations fournies par les échanges sont traitées pour identifier les différents types de données (comme les mises à jour en profondeur, les transactions, etc.) et les formater en objets d'événements unifiés.3. Données d'abonnement
À chaque connexion, le système trace les canaux de données de marché liés aux abonnements en fonction de la transaction en cours.
getFunction
Les fonctionsLes fonctions de traitement correspondantes sont obtenues selon le nom de l'échange.this.wssPublic
Les fonctions: initialize la connexion WebSocket et démarre la réception de données.4. Gestion des fils
Pour chaque échange, un fil est lancé, les données sont reçues en temps réel et traitées par des fonctions de rappel.
threadMarket
Les fonctions: recevoir des données, analyser et stocker les dernières informations de profondeur et de transaction dans les sous-thèmes;5. Réécrire les méthodes d'acquisition des données
Pour chaque échange, réécrivez la méthode d'accès à la profondeur et à l'information sur les transactions, en donnant la priorité aux données mises à jour en temps réel.
$.setupWebsocket()
Initializez la connexion WebSocket de l'échange cible.GetDepth()
etGetTrades()
Fonction qui utilise automatiquement les données WebSocket en temps réel pour effectuer des retours de profondeur de marché et de transaction.Si vous ajoutez la fonction EventLoop à votre stratégie, elle se transforme en un mécanisme de déclenchement qui est automatiquement récupéré immédiatement lorsque les données sont mises à jour.
function main() {
$.setupWebsocket()
while (true) {
exchanges.map(e=>{
Log(e.GetName(), e.GetDepth())
Log(e.GetName(), e.GetTrades())
})
EventLoop(100) // trigger by websocket
}
}
Je vous invite à lire mon précédent guide sur les stratégies de trading en devises.https://www.fmz.com/digest-topic/10506Il est très facile de modifier la prise en charge de WebSocket ici:
function MakeOrder() {
for (let i in Info.trade_symbols) {
let symbol = Info.trade_symbols[i];
let buy_price = exchange.GetDepth(symbol + '_USDT').Asks[0].Price;
let buy_amount = 50 / buy_price;
if (Info.position[symbol].value < 2000){
Trade(symbol, "buy", buy_price, buy_amount, symbol);
}
}
}
function OnTick() {
try {
UpdatePosition();
MakeOrder();
UpdateStatus();
} catch (error) {
Log("循环出错: " + error);
}
}
function main() {
$.setupWebsocket()
InitInfo();
while (true) {
let loop_start_time = Date.now();
if (Date.now() - Info.time.last_loop_time > Info.interval * 1000) {
OnTick();
Info.time.last_loop_time = Date.now();
Info.time.loop_delay = Date.now() - loop_start_time;
}
Sleep(5);
}
}
Si vous suivez le modèle de la stratégie, vous pouvez copier le format ci-dessous et consulter la documentation de l'API de l'échange:
supports["Binance"] = function (ctx:ICtx) {
let processMsg = function (obj) {
let event = {
ts: obj.E,
instId: obj.s,
depth: null,
trades: [],
}
if (obj.e == "depthUpdate") {
let depth = {
asks: [],
bids: []
}
obj.b.forEach(function (item) {
depth.bids.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
obj.a.forEach(function (item) {
depth.asks.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
event.depth = depth
} else if (obj.e == 'bookTicker') {
event.depth = {
asks: [{ price: Number(obj.a), qty: Number(obj.A) }],
bids: [{ price: Number(obj.b), qty: Number(obj.B) }]
}
} else if (obj.e == 'aggTrade') {
event.ts = obj.E
event.trades = [{
price: Number(obj.p),
qty: Number(obj.q),
ts: obj.T,
side: obj.m ? "sell" : "buy"
}]
} else if (typeof (obj.asks) !== 'undefined') {
event.ts = obj.E || new Date().getTime()
let depth = {
asks: [],
bids: []
}
obj.bids.forEach(function (item) {
depth.bids.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
obj.asks.forEach(function (item) {
depth.asks.push({
price: Number(item[0]),
qty: Number(item[1])
})
})
event.depth = depth
} else {
return
}
return event
}
let channels = ["depth20@100ms", /*"bookTicker", */"aggTrade"]
let ws = null
let endPoint = "wss://stream.binance.com/stream"
if (ctx.name == "Futures_Binance") {
endPoint = "wss://fstream.binance.com/stream"
}
while (true) {
if (!ws) {
let subscribes = []
ctx.symbols.forEach(function (symbol) {
channels.forEach(function (channel) {
subscribes.push(symbol.toLowerCase() + "@" + channel)
})
})
ws = MyDial(endPoint + (subscribes.length > 0 ? ("?streams=" + subscribes.join("/")) : ""))
}
if (!ws) {
Sleep(1000)
continue
}
updateSymbols(ctx, function(symbol:string, method:string) {
ws.write(JSON.stringify({
"method": method.toUpperCase(),
"params": channels.map(c=>symbol.toLowerCase()+'@'+c),
"id": 2
}))
})
let msg = ws.read(1000)
if (!msg) {
if (msg == "") {
trace("websocket is closed")
ws.close()
ws = null
}
continue
}
if (msg == 'ping') {
ws.write('pong')
} else if (msg == 'pong') {
} else {
let obj = JSON.parse(msg)
if (obj.error) {
trace(obj.error.msg, "#ff0000")
continue
}
if (!obj.stream) {
continue
}
if (obj.stream.indexOf("depth") != -1) {
if (typeof(obj.data.s) !== 'string') {
// patch
obj.data.s = obj.stream.split('@')[0].toUpperCase()
}
}
let event = processMsg(obj.data)
if (event) {
ctx.callback(event)
}
}
}
}