Escribí un artículo en 2020 introduciendo estrategias de comercio de alta frecuencia (https://www.fmz.com/digest-topic/6228Aunque recibió cierta atención, no fue muy profunda. Han pasado dos años y medio desde entonces, y el mercado ha cambiado. Después de publicar ese artículo, mi estrategia de alta frecuencia fue capaz de obtener ganancias estables durante mucho tiempo, pero las ganancias disminuyeron gradualmente e incluso se detuvieron en un momento dado. En los últimos meses, he dedicado tiempo a reformarla, y todavía puede obtener algunas pequeñas ganancias.
Cuentas de reembolsos de la Comisión
Si el volumen de negociación diario es de 100 millones de U, el reembolso es de 5,000 U. Por supuesto, la tarifa del tomador todavía depende de la tasa VIP, por lo que si la estrategia no necesita tomar órdenes, el nivel VIP tiene poco efecto en la estrategia de alta frecuencia. Diferentes niveles de reembolsos de comisión están disponibles en diferentes intercambios, lo que requiere un alto volumen de negociación. En los primeros días, todavía había ganancias que se podían obtener sin reembolsos, pero a medida que se intensificaba la competencia, los reembolsos representaban una mayor proporción de las ganancias y los operadores de alta frecuencia perseguían las tasas más altas.
Velocidad
El comercio de alta frecuencia se llama así debido a su velocidad rápida. Unirse a un servidor de colocación de un intercambio comercial y obtener la latencia más baja y la conexión más estable se ha convertido en una de las condiciones de competencia. El tiempo de procesamiento interno de la estrategia también debe ser lo más bajo posible.
Mercado adecuado
El comercio de alta frecuencia se considera la joya de la corona del comercio cuantitativo, y creo que muchos comerciantes algorítmicos lo han intentado, pero la mayoría de la gente debería haberlo dejado porque no podían ganar dinero y no podían encontrar una manera de mejorar. La razón principal es probablemente porque eligieron el mercado de comercio equivocado. En la etapa inicial de la estrategia, los mercados relativamente fáciles deben dirigirse al comercio para obtener ganancias y recibir retroalimentación para mejorar, lo que es propicio para el progreso de la estrategia. Si comienzas en el mercado más ferozmente competitivo y compites con muchos oponentes, perderás dinero sin importar lo duro que lo intentes, y te rendirás rápidamente. Recomiendo comenzar con pares de comercio de contratos perpetuos recién lanzados, donde hay menos competidores, especialmente aquellos con volúmenes comerciales relativamente grandes, lo que facilita hacer dinero. BTC y ETH tienen los volúmenes más altos y más activos, pero también son los más difíciles de sobrevivir en el potencial.
Enfrenta a la competencia de frente
El mercado para cualquier negociación está cambiando constantemente, y ninguna estrategia de negociación puede ser una solución de una sola vez. Esto es aún más obvio en el comercio de alta frecuencia, donde ingresar al mercado significa competir directamente con los operadores más inteligentes y diligentes. En un mercado de juego de suma cero, cuanto más gane, menos ganan los demás. Cuanto más tarde entre, más difícil se vuelve, y los que ya están en el mercado deben mejorar constantemente y podrían eliminarse en cualquier momento. Hace tres o cuatro años probablemente fue la mejor oportunidad, pero con la reciente disminución general de la actividad en el mercado de divisas digitales, se ha vuelto muy difícil para los principiantes comenzar a hacer comercio de alta frecuencia.
Existen varias estrategias de trading de alta frecuencia, como el arbitraje de alta frecuencia, que implica encontrar oportunidades de arbitraje a través de este u otros intercambios, aprovechando la oportunidad de comer órdenes antes que otros y obtener ganancias con ventaja de velocidad; el trading de tendencias de alta frecuencia, que implica beneficiarse de tendencias a corto plazo; y el marketing, que implica colocar órdenes en ambos lados de las operaciones de compra y venta, controlar bien las posiciones y obtener ganancias a través de descuentos de comisión.
El siguiente código se basa en la arquitectura básica del contrato perpetuo de Binance y se suscribe principalmente a las operaciones de flujo de pedidos de profundidad del websocket e información de posición. Dado que los datos de mercado e información de la cuenta se suscriben por separado, se necesita leer (-1) continuamente para determinar si se ha obtenido la información más reciente. Aquí, EventLoop (1000) se utiliza para evitar bucles muertos directos y reducir la carga del sistema. EventLoop (1000) bloquea hasta que haya un wss o retorno de tareas concurrentes, con un tiempo de espera 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 is the pair of symbol
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)
}
}
Como se mencionó anteriormente, mi estrategia de alta frecuencia requiere primero identificar tendencias antes de ejecutar operaciones de compra y venta. Juzgar las tendencias a corto plazo se basa principalmente en los datos de transacción, es decir, el aggTrade suscrito, que incluye la dirección, el precio, la cantidad y el tiempo de la transacción. Las operaciones de compra y venta se refieren principalmente a la profundidad y el volumen de transacción. Los siguientes son indicadores detallados que deben considerarse, la mayoría de los cuales se dividen en dos grupos para comprar y vender y se cuentan dinámicamente dentro de una cierta ventana de tiempo.
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 el último precio de compra es mayor que el precio de venta promedio y el último precio de oferta es mayor que el precio de oferta promedio y el valor de la orden de compra es mayor que el valor de la orden de venta en un intervalo fijo, se considera que se trata de un mercado alcista a corto plazo.
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]
}
Aquí, el viejo método de iteración de la profundidad a la cantidad requerida todavía se utiliza. Suponiendo que una orden de compra que puede ser ejecutada por 10 monedas en 1 segundo y sin considerar la situación de nuevas órdenes, el precio de venta se establece en la posición donde la orden de compra
El tamaño de la ventana de tiempo específica debe ser fijado por uno mismo.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
La relación representa una proporción fija de la cantidad de la última orden de venta, representando la cantidad de la orden de compra como una proporción fija de la cantidad de la última orden de venta.
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)
}
Entre ellos, el avg_diff es la diferencia promedio en el spread, y solo cuando la diferencia de compra y venta en la colocación de órdenes es mayor que un cierto múltiplo de este valor y el mercado es alcista, se colocará una orden de compra. Si se mantiene una posición corta, la posición también se cerrará para evitar mantener la posición durante mucho tiempo. Se pueden colocar órdenes solo para garantizar que se cumplan las órdenes, y se pueden usar ID de orden personalizadas para evitar esperar las devoluciones de órdenes.
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)//未返回的任务下次继续等待
}
})
jobs = new_jobs
tasks = []
}
/*
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()]})
*/