Escribí un artículo en 2020 introduciendo estrategias de alta frecuencia,https://www.fmz.com/bbs-topic/9750. Aunque recibió bastante atención, no fue muy profunda. Han pasado más de dos años desde entonces, y el mercado ha cambiado. Después de que se publicó ese artículo, mi estrategia de alta frecuencia podría obtener ganancias establemente durante mucho tiempo, pero gradualmente, las ganancias disminuyeron e incluso se detuvieron en un punto. En los últimos meses he invertido algún esfuerzo para reformarla, y ahora todavía puede obtener algunos beneficios. En este artículo, proporcionaré una introducción más detallada de mis ideas de estrategia de alta frecuencia y un código simplificado como punto de partida para las comunicaciones; y las opiniones son bienvenidas.
Si el monto de la transacción diaria es de 100 millones de U, el reembolso será de 5000 U. Por supuesto, las tarifas de los tomadores todavía se basan en tasas VIP, por lo que si la estrategia no requiere tomadores, el nivel VIP tiene poco impacto en las estrategias de alta frecuencia. Diferentes niveles de intercambios generalmente tienen diferentes tasas de reembolso y requieren mantener un alto monto de transacción. En los primeros tiempos, cuando algunos mercados de divisas fluctuaron mucho, hubo ganancias incluso sin reembolsos. A medida que la competencia se intensificó, los reembolsos representaron una mayor proporción de las ganancias o incluso dependían únicamente de ellos; los operadores de alta frecuencia persiguieron tarifas de alto nivel.
Velocidad. La razón por la que las estrategias de alta frecuencia se llaman de alta frecuencia es porque son muy rápidas. Unirse al servidor de color del exchange, obtener la menor latencia y la conexión más estable también se ha convertido en una de las condiciones para la competencia interna. El tiempo de consumo interno de la estrategia debe ser lo menos posible, y este artículo presentará el marco websocket que uso, que adopta la ejecución concurrente.
Mercado adecuado. El comercio de alta frecuencia se conoce como la perla del comercio cuantitativo, y muchos comerciantes programáticos lo han intentado, pero la mayoría de la gente se detuvo porque no pueden obtener ganancias y no pueden encontrar una dirección para mejorar. La razón principal debe ser que eligieron el mercado comercial equivocado. En la etapa inicial del desarrollo de la estrategia, se deben elegir mercados relativamente fáciles para obtener ganancias en el comercio para que haya ganancias y retroalimentación para mejorar, lo que es propicio para el progreso de la estrategia. Si comienzas a competir en el mercado más competitivo con muchos oponentes potenciales, sin importar lo duro que lo intentes pronto, perderás dinero y te rendirás. Recomiendo nuevos pares de comercio de contratos perpetuos cuando no haya tantos competidores, especialmente aquellos con una cantidad de transacción relativamente grande; es cuando es más fácil obtener ganancias. BTC y ETH tienen la mayor cantidad de transacciones y son más activos en la cantidad de transacciones, pero también son más difíciles de sobrevivir.
Enfrentar la competencia. El mercado para cualquier transacción está cambiando constantemente, y ninguna estrategia de negociación puede durar para siempre, especialmente en el comercio de alta frecuencia. Entrar en este 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 ganarán los demás. Cuanto más tarde entre, mayor será la dificultad; los que ya están en el mercado también deben mejorar continuamente. Hace 3-4 años fue probablemente la mejor oportunidad; recientemente, la actividad general en los mercados de divisas digitales ha disminuido, lo que hace muy difícil para los recién llegados comenzar a operar con alta frecuencia ahora.
Hay varias estrategias de alta frecuencia:
El siguiente código se basa en el marco básico de los contratos perpetuos de Binance, suscribiendo principalmente a la profundidad del websocket, los datos de mercado de flujo de órdenes de profundidad y la información de posición. Dado que los datos de mercado e información de la cuenta se suscriben por separado, es necesario usar read ((-1) continuamente para determinar si se ha obtenido la información más reciente. Aquí EventLoop ((1000) se utiliza para evitar bucles interminables directos y reducir la carga del sistema. EventLoop ((1000) bloqueará hasta que haya wss o devoluciones 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 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)
}
}
Como se mencionó anteriormente, mi estrategia de alta frecuencia requiere determinar la tendencia antes de ejecutar la compra y venta. La tendencia a corto plazo se juzga principalmente sobre la base de los datos de transacción tick-by-tick, es decir, el aggTrade en la suscripción, que incluye la dirección de la transacción, precio, cantidad, tiempo de transacción, etc. La compra y venta se refieren principalmente a la profundidad y el monto de la operación. Las siguientes son introducciones detalladas de los indicadores a tener en cuenta; la mayoría de ellos se dividen en grupos de compra y venta y se cuentan dinámicamente dentro de una cierta ventana de tiempo. La ventana de tiempo de mi estrategia es dentro de 10 segundos.
//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 el precio de venta más reciente es superior al precio de venta promedio, el precio de compra más reciente es superior al precio de compra promedio y el valor de la orden de compra de intervalo fijo es mayor que el valor de la orden de venta, entonces se considera 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í, todavía adoptamos el antiguo enfoque, iterando hasta la profundidad requerida. Suponiendo que 10 monedas se pueden negociar en 1 segundo, sin considerar nuevas órdenes pendientes, el precio de venta se establece en la posición donde se compran 10 monedas. El tamaño específico de la ventana de tiempo debe ser establecido por usted mismo.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
La proporción representa una proporción fija, lo que significa que la cantidad de la orden de compra es una proporción fija de la cantidad reciente de la 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)
}
donde avg_diff es la diferencia de precio promedio del mercado, y una orden de compra solo se colocará cuando el diferencial de oferta y demanda sea mayor que un cierto múltiplo de este valor y sea alcista. Si mantiene una posición corta, también cerrará la posición para evitar mantenerla por un período prolongado. Las órdenes se pueden colocar como órdenes de un solo fabricante para garantizar que se ejecuten. Además, se puede usar el ID de orden personalizado de Binance para que no sea necesario esperar la respuesta de la orden.
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()]})
*/