J'ai écrit un article en 2020 présentant des stratégies à haute fréquence,https://www.fmz.com/bbs-topic/9750. Bien qu'il ait reçu un peu d'attention, il n'a pas été très en profondeur. Plus de deux ans se sont écoulés depuis lors, et le marché a changé. Après la publication de cet article, ma stratégie à haute fréquence a pu générer des profits stables pendant longtemps, mais progressivement, les profits ont diminué et ont même cessé à un moment donné. Au cours des derniers mois, j'ai déployé quelques efforts pour la rénover, et maintenant elle peut encore générer des profits. Dans cet article, je vais fournir une introduction plus détaillée de mes idées de stratégie à haute fréquence et un code simplifié comme point de départ pour les communications; et les commentaires sont les bienvenus.
Bien sûr, les frais des preneurs sont toujours basés sur des taux VIP, donc si la stratégie ne nécessite pas de preneurs, le niveau VIP a peu d'impact sur les stratégies à haute fréquence. Différents niveaux d'échanges ont généralement des taux de rabais différents et nécessitent de maintenir un montant de transaction élevé. Au début, lorsque certains marchés de devises fluctuaient considérablement, il y avait des profits même sans rabais.
La vitesse. La raison pour laquelle les stratégies à haute fréquence sont appelées à haute fréquence est parce qu'elles sont très rapides. Joindre le serveur de colo de l'échange, obtenir la plus faible latence et la connexion la plus stable est également devenue l'une des conditions de la concurrence interne. Le temps de consommation interne de la stratégie devrait être le moins possible, et cet article présentera le framework websocket que j'utilise, qui adopte l'exécution simultanée.
Marché approprié. Le trading à haute fréquence est connu comme la perle du trading quantitatif, et de nombreux traders programmatiques l'ont essayé, mais la plupart des gens ont arrêté parce qu'ils ne peuvent pas faire de profit et ne peuvent pas trouver une direction pour l'amélioration. La raison principale devrait être qu'ils ont choisi le mauvais marché de trading. Au stade initial du développement de la stratégie, des marchés relativement faciles devraient être choisis pour réaliser des profits dans le trading afin qu'il y ait des profits et des commentaires pour l'amélioration, ce qui est propice à la progression de la stratégie. Si vous commencez à rivaliser sur le marché le plus compétitif avec de nombreux adversaires potentiels, peu importe à quel point vous essayez, vous perdrez de l'argent et abandonnez bientôt. Je recommande les paires de trading de contrats perpétuels nouvellement cotés quand il n'y a pas autant de concurrents, en particulier ceux avec un montant de transaction relativement important; c'est à ce moment-là que les profits sont les plus faciles.
Face à la concurrence. Le marché pour toute transaction est en constante évolution, et aucune stratégie de trading ne peut durer éternellement, en particulier dans le trading à haute fréquence. Entrer sur ce marché signifie concurrencer directement les traders les plus intelligents et les plus diligents. Dans un marché de jeu à somme nulle, plus vous gagnez, moins les autres gagneront. Plus vous entrez tard, plus la difficulté est élevée; ceux qui sont déjà sur le marché doivent également s'améliorer continuellement. Il y a 3-4 ans, c'était probablement la meilleure opportunité; récemment, l'activité globale sur les marchés de la monnaie numérique a diminué, ce qui rend très difficile pour les nouveaux arrivants de commencer le trading à haute fréquence maintenant.
Il existe différentes stratégies de haute fréquence:
Le code suivant est basé sur le cadre de base des contrats perpétuels de Binance, souscrivant principalement à la profondeur du websocket, aux données de marché des transactions de flux d'ordres de profondeur et aux informations de position. Puisque les données de marché et les informations de compte sont souscrites séparément, il est nécessaire d'utiliser read ((-1) en continu pour déterminer si les dernières informations ont été obtenues. Ici, EventLoop ((1000) est utilisé pour éviter les boucles sans fin directes et réduire la charge du système. EventLoop ((1000) bloquera jusqu'à ce qu'il y ait des retours de tâches wss ou simultanés avec un temps d'arrêt de 1000 ms.
var datastream = null
var tickerstream = null
var update_listenKey_time = 0
function ConncetWss(){
if (Date.now() - update_listenKey_time < 50*60*1000) {
return
}
if(datastream || tickerstream){
datastream.close()
tickerstream.close()
}
//Need APIKEY
let req = HttpQuery(Base+'/fapi/v1/listenKey', {method: 'POST',data: ''}, null, 'X-MBX-APIKEY:' + APIKEY)
let listenKey = JSON.parse(req).listenKey
datastream = Dial("wss://fstream.binance.com/ws/" + listenKey + '|reconnect=true', 60)
//Symbols are the set trading pairs
let trade_symbols_string = Symbols.toLowerCase().split(',')
let wss_url = "wss://fstream.binance.com/stream?streams="+trade_symbols_string.join(Quote.toLowerCase()+"@aggTrade/")+Quote.toLowerCase()+"@aggTrade/"+trade_symbols_string.join(Quote.toLowerCase()+"@depth20@100ms/")+Quote.toLowerCase()+"@depth20@100ms"
tickerstream = Dial(wss_url+"|reconnect=true", 60)
update_listenKey_time = Date.now()
}
function ReadWss(){
let data = datastream.read(-1)
let ticker = tickerstream.read(-1)
while(data){
data = JSON.parse(data)
if (data.e == 'ACCOUNT_UPDATE') {
updateWsPosition(data)
}
if (data.e == 'ORDER_TRADE_UPDATE'){
updateWsOrder(data)
}
data = datastream.read(-1)
}
while(ticker){
ticker = JSON.parse(ticker).data
if(ticker.e == 'aggTrade'){
updateWsTrades(ticker)
}
if(ticker.e == 'depthUpdate'){
updateWsDepth(ticker)
}
ticker = tickerstream.read(-1)
}
makerOrder()
}
function main() {
while(true){
ConncetWss()
ReadWss()
worker()
updateStatus()
EventLoop(1000)
}
}
Comme mentionné précédemment, ma stratégie à haute fréquence nécessite de déterminer la tendance avant d'exécuter l'achat et la vente. La tendance à court terme est principalement jugée sur la base des données de transaction tick-by-tick, c'est-à-dire de l'aggTrade dans l'abonnement, qui comprend la direction de la transaction, le prix, la quantité, le temps de transaction, etc. L'achat et la vente se réfèrent principalement à la profondeur et au montant des transactions. Voici des introductions détaillées des indicateurs à prendre en compte; la plupart d'entre eux sont divisés en groupes d'achat et de vente et sont comptés dynamiquement dans une certaine fenêtre de temps.
//bull represents short-term bullish, bear represents short-term bearish
let bull = last_sell_price > avg_sell_price && last_buy_price > avg_buy_price &&
avg_buy_amount / avg_buy_time > avg_sell_amount / avg_sell_time;
let bear = last_sell_price < avg_sell_price && last_buy_price < avg_buy_price &&
avg_buy_amount / avg_buy_time < avg_sell_amount / avg_sell_time;
Si le prix de vente le plus récent est supérieur au prix de vente moyen, le prix d'achat le plus récent est supérieur au prix d'achat moyen et la valeur de l'ordre d'achat à intervalles fixes est supérieure à la valeur de l'ordre de vente, alors il est jugé haussier à court terme.
function updatePrice(depth, bid_amount, ask_amount) {
let buy_price = 0
let sell_price = 0
let acc_bid_amount = 0
let acc_ask_amount = 0
for (let i = 0; i < Math.min(depth.asks.length, depth.bids.length); i++) {
acc_bid_amount += parseFloat(depth.bids[i][1])
acc_ask_amount += parseFloat(depth.asks[i][1])
if (acc_bid_amount > bid_amount && buy_price == 0) {
buy_price = parseFloat(depth.bids[i][0]) + tick_size
}
if (acc_ask_amount > ask_amount && sell_price == 0) {
sell_price = parseFloat(depth.asks[i][0]) - tick_size
}
if (buy_price > 0 && sell_price > 0) {
break
}
}
return [buy_price, sell_price]
}
Ici, nous adoptons toujours l'ancienne approche, en itérant à la profondeur requise. En supposant que 10 pièces peuvent être échangées en 1 seconde, sans tenir compte de nouveaux ordres en attente, le prix de vente est fixé à la position où 10 pièces sont achetées. La taille spécifique de la fenêtre de temps doit être définie par vous-même.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
Le ratio représente une proportion fixe, ce qui signifie que la quantité d'ordre d'achat est une proportion fixe de la quantité récente d'ordre de vente.
if(bull && (sell_price-buy_price) > N * avg_diff) {
trade('buy', buy_price, buy_amount)
}else if(position.amount < 0){
trade('buy', buy_price, -position.amount)
}
if(bear && (sell_price-buy_price) > N * avg_diff) {
trade('sell', sell_price, sell_amount)
}else if(position.amount > 0){
trade('sell', sell_price, position.amount)
}
où avg_diff est la différence moyenne de prix du marché, et un ordre d'achat ne sera placé que lorsque l'écart offre-demande est supérieur à un certain multiple de cette valeur et qu'il est haussier. Si vous maintenez une position courte, il fermera également la position pour éviter de la maintenir pendant une période prolongée. Les ordres peuvent être placés en tant qu'ordres de fabricant unique pour s'assurer qu'ils sont exécutés. En outre, l'ID d'ordre personnalisé de Binance
var tasks = []
var jobs = []
function worker(){
let new_jobs = []
for(let i=0; i<tasks.length; i++){
let task = tasks[i]
jobs.push(exchange.Go.apply(this, task.param))
}
_.each(jobs, function(t){
let ret = t.wait(-1)
if(ret === undefined){
new_jobs.push(t)//Unreturned tasks will continue to wait next time
}
})
jobs = new_jobs
tasks = []
}
/*
Write the required task parameters in param
tasks.push({'type':'order','param': ["IO", "api", "POST","/fapi/v1/order",
"symbol="+symbol+Quote+"&side="+side+"&type=LIMIT&timeInForce=GTX&quantity="+
amount+"&price="+price+"&newClientOrderId=" + UUID() +"×tamp="+Date.now()]})
*/