Escrevi um artigo em 2020 a introduzir estratégias de alta frequência,https://www.fmz.com/bbs-topic/9750. Embora tenha recebido bastante atenção, não foi muito aprofundado. Mais de dois anos se passaram desde então, e o mercado mudou. Depois que esse artigo foi publicado, minha estratégia de alta frequência poderia gerar lucros de forma estável por um longo tempo, mas gradualmente, os lucros diminuíram e até pararam em um ponto. Nos últimos meses, eu gastei algum esforço para renová-lo, e agora ele ainda pode gerar alguns lucros. Neste artigo, fornecerei uma introdução mais detalhada às minhas ideias de estratégia de alta frequência e algum código simplificado como ponto de partida para discussões; e os comentários são bem-vindos.
As contas de desconto, tomando o Binance como exemplo, têm um desconto do fabricante de 0,0005% atualmente. Se o valor da transação diária for de 100 milhões de U, o desconto será de 5000 U. Claro, as taxas de tomada ainda são baseadas em taxas VIP, então se a estratégia não exigir tomadas, o nível VIP tem pouco impacto nas estratégias de alta frequência. Diferentes níveis de exchanges geralmente têm taxas de desconto diferentes e exigem manter um alto valor de transação. Nos primeiros tempos, quando alguns mercados de moeda flutuavam muito, havia lucros mesmo sem descontos. À medida que a concorrência se intensificava, os descontos representavam uma maior proporção dos lucros ou até mesmo dependiam apenas deles; os comerciantes de alta frequência buscavam taxas de alto nível.
Velocidade. A razão pela qual as estratégias de alta frequência são chamadas de alta frequência é porque elas são muito rápidas. Juntar-se ao servidor de cores do exchange, obter a menor latência e a conexão mais estável também se tornou uma das condições para a concorrência interna. O tempo de consumo interno da estratégia deve ser o menor possível, e este artigo apresentará a estrutura de websocket que uso, que adota execução simultânea.
Mercado adequado. A negociação de alta frequência é conhecida como a pérola da negociação quantitativa, e muitos comerciantes programáticos a tentaram, mas a maioria das pessoas parou porque não conseguem lucrar e não conseguem encontrar uma direção para melhorar. A principal razão deve ser que eles escolheram o mercado de negociação errado. No estágio inicial do desenvolvimento da estratégia, mercados relativamente fáceis devem ser escolhidos para lucrar na negociação para que haja lucros e feedback para melhoria, o que é propício ao progresso da estratégia. Se você começar a competir no mercado mais competitivo com muitos oponentes potenciais, não importa o quanto você tente, você perderá dinheiro e desistirá logo.
Enfrentar a concorrência. O mercado para qualquer transação está mudando constantemente, e nenhuma estratégia de negociação pode durar para sempre, especialmente na negociação de alta frequência. Entrar neste mercado significa competir diretamente com os comerciantes mais inteligentes e mais diligentes. Em um mercado de jogo de soma zero, quanto mais você ganhar, menos os outros ganharão. Quanto mais tarde você entrar, maior a dificuldade; aqueles que já estão no mercado também devem melhorar continuamente.
Existem várias estratégias de alta frequência:
O código a seguir é baseado na estrutura básica dos contratos perpétuos da Binance, subscrevendo principalmente a profundidade do websocket, dados de mercado de fluxo de ordens de profundidade e informações de posição. Como os dados de mercado e as informações da conta são assinados separadamente, é necessário usar read ((-1) continuamente para determinar se as informações mais recentes foram obtidas.
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 mencionado anteriormente, minha estratégia de alta frequência requer determinar a tendência antes de executar a compra e venda. A tendência de curto prazo é julgada principalmente com base em dados de transação tick-by-tick, ou seja, o aggTrade na assinatura, que inclui direção da transação, preço, quantidade, tempo de transação, etc. A compra e venda se referem principalmente à profundidade e ao valor da negociação. A seguir estão introduções detalhadas dos indicadores a serem considerados; a maioria deles é dividida em grupos de compra e venda e é contada dinamicamente dentro de uma determinada janela de tempo. A janela de tempo da minha estratégia é 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;
Se o último preço de venda for superior ao preço médio de venda, o último preço de compra for superior ao preço médio de compra e o valor da ordem de compra de intervalo fixo for superior ao valor da ordem de venda, então é julgado de alta a curto prazo.
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 velha abordagem, iterando até a profundidade necessária. Supondo que 10 moedas possam ser negociadas em 1 segundo, sem considerar novas ordens pendentes, o preço de venda é definido na posição onde 10 moedas são compradas. O tamanho específico da janela de tempo precisa ser definido por você mesmo.
let buy_amount = Ratio * avg_sell_amount / avg_sell_time
let sell_amount = Ratio * avg_buy_amount / avg_buy_time
A proporção representa uma proporção fixa, o que significa que a quantidade da ordem de compra é uma proporção fixa da quantidade da ordem de venda recente.
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)
}
onde avg_diff é a diferença média de preço de mercado, e uma ordem de compra só será colocada quando o spread bid-ask for maior que um certo múltiplo deste valor e for de alta. Se manter uma posição curta, também fechará a posição para evitar a detenção por um período prolongado. As ordens podem ser colocadas como ordens de um único fabricante para garantir que sejam executadas. Além disso, o ID de ordem personalizado da Binance pode ser usado para que não haja necessidade de esperar pela resposta da ordem.
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()]})
*/