
[TOC] Em 2020, escrevi um artigo apresentando estratégias de alta frequência, https://www.fmz.com/digest-topic/6228. Embora tenha recebido muita atenção, não foi escrito em profundidade. Mais de dois anos se passaram e o mercado mudou. Depois que esse artigo foi publicado, minha estratégia de alta frequência conseguiu gerar dinheiro de forma constante por um longo tempo, mas os lucros diminuíram gradualmente e até pararam em um ponto. Nos últimos meses, tenho me esforçado muito na reforma e atualmente consigo ganhar algum dinheiro. Este artigo apresentará minhas ideias para estratégias de alta frequência e alguns códigos simplificados em mais detalhes, que podem servir como um ponto de partida para discussão. Todos são bem-vindos para se comunicar e dar feedback.
Para contas que recebem descontos, tomando a Binance como exemplo, o desconto atual do maker é de 0,5% de 100.000. Se o volume diário de transações for de 100 milhões de U, o desconto será de 5.000 U. É claro que a taxa de aceitação ainda é baseada na taxa VIP, então, se a estratégia não exigir a aceitação de ordens, o nível VIP terá pouco impacto em estratégias de alta frequência. Geralmente, diferentes níveis de trocas têm diferentes taxas de desconto e exigem a manutenção de um volume maior de transações. Há muito tempo, quando o mercado de algumas moedas flutuava muito, ainda havia lucro mesmo sem descontos. Com a intensificação da circulação interna, os descontos representavam uma grande proporção dos lucros e até dependiam inteiramente de descontos. Os traders de alta frequência buscam Melhores tarifas.
velocidade. A razão pela qual a estratégia de alta frequência é chamada de alta frequência é porque ela é muito rápida. Aderir ao servidor colo da exchange para obter a menor latência e a conexão mais estável também se tornou uma das condições para a circulação interna. O consumo interno de tempo da estratégia também deve ser o mais baixo possível. Este artigo apresentará o framework websocket que eu uso, que usa execução concorrente.
O mercado certo. O trading de alta frequência é conhecido como a joia do trading quantitativo. Acredito que muitos traders programáticos já tentaram, mas a maioria das pessoas provavelmente para porque não ganham dinheiro e não conseguem encontrar uma maneira de melhorar. O principal motivo é provavelmente que eles estão procurando o caminho errado. O mercado de negociação. No estágio inicial de uma estratégia, deve-se procurar mercados relativamente fáceis para ganhar dinheiro negociando, para que haja lucros e feedback sobre melhorias, o que será propício ao avanço da estratégia. Se você começar no mercado mais competitivo e competir com muitos rivais em potencial, você perderá dinheiro, não importa o quanto tente, e não conseguirá mais se manter firme. Recomendo os pares de negociação de contratos perpétuos recém-listados. Neste momento, não há tantos concorrentes, especialmente quando o volume de negociação é relativamente grande. Este é o momento mais fácil para ganhar dinheiro. BTC e ETH têm o maior volume de negociação e as transações mais ativas, mas também são os mais difíceis de sobreviver.
Enfrente a concorrência de frente. Qualquer mercado de negociação está mudando dinamicamente. Nenhuma estratégia de negociação pode funcionar de uma vez por todas. Isso é ainda mais óbvio na negociação de alta frequência. Entrar neste mercado significa competir diretamente contra um grupo dos traders mais inteligentes e diligentes. Em um mercado de soma zero, quanto mais você ganha, menos os outros ganham. Quanto mais tarde você entrar, mais difícil será. Aqueles que já estão no mercado também devem continuar melhorando, pois podem ser eliminados a qualquer momento. Três ou quatro anos atrás deveria ter sido a melhor oportunidade. Recentemente, a atividade geral do mercado de moeda digital declinou, e agora é muito difícil para novatos se envolverem em negociações de alta frequência.
Existem muitos tipos de estratégias de alta frequência
Minha estratégia é uma combinação de tendência e formador de mercado. Primeiro determino a tendência, depois coloco uma ordem e imediatamente coloco uma ordem de venda após a transação ser concluída. Não mantenho posições de estoque. O código da estratégia é apresentado abaixo.
O código a seguir é baseado na arquitetura básica dos contratos perpétuos da Binance e assina principalmente informações de mercado, posições e fluxos de ordens em profundidade do websocket. Como as informações de mercado e as informações de conta são assinadas separadamente, é necessário usar read(-1) continuamente para determinar se as informações mais recentes são obtidas. EventLoop(1000) é usado aqui para evitar um loop infinito direto e reduzir a carga do sistema. EventLoop(1000) será bloqueado até que wss ou tarefas simultâneas retornem, com um tempo limite 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()
}
//需要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是设定的交易对
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 mencionado anteriormente, minha estratégia de alta frequência exige determinar a tendência antes de executar compras e vendas. A tendência de curto prazo é julgada principalmente com base nos dados de transação de cada transação, ou seja, aggTrade na assinatura, que inclui direção da transação, preço, quantidade, tempo da transação, etc. As principais referências para compra e venda são profundidade e volume de negociação. A seguir, uma introdução detalhada aos indicadores que precisam de atenção. A maioria dos indicadores é dividida em dois grupos: comprar e vender, e são contados dinamicamente em uma determinada janela de tempo. A janela de tempo da minha estratégia é de 10 segundos.
//bull代表短期看涨,bear短期看跌
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;
Se o último preço de venda for maior que o preço médio de venda, o último preço de compra for maior que o preço médio de compra e o valor da ordem de compra em intervalo fixo for maior que o valor da ordem de venda, então é considerado otimista de curto prazo. . Pelo contrário, é pessimista.
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]
}
Aqui ainda adotamos a ideia antiga e iteramos a profundidade para a quantidade necessária. Aqui assumimos que uma ordem de compra de 10 moedas pode ser executada em 1 segundo. Sem considerar novas ordens pendentes, o preço da ordem de venda é definido para a posição onde o a ordem de compra de 10 moedas será atingida. Você precisa definir o intervalo de tempo específico.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
Proporção significa Proporção Fixa, o que significa que a quantidade da ordem de compra é uma proporção fixa da quantidade da ordem de venda mais recente. Essa estratégia pode ajustar de forma adaptativa o tamanho do pedido com base na atividade atual de compra e venda.
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 eles, avg_diff é a diferença do preço médio de mercado. Uma ordem de compra só será colocada quando o spread bid-ask for maior que um certo múltiplo desse valor e a tendência for de alta. Se você mantiver uma ordem curta, a posição também será fechado neste momento para evitar retenção de longo prazo do pedido. Você pode colocar uma ordem only-maker para garantir que a ordem pendente seja executada. E você pode usar o ID de pedido personalizado da Binance, para não precisar esperar o pedido ser devolvido.
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 = []
}
/*
需要的任务参数写在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()]})
*/