Na primeira parte deste tutorial, que você confere neste link, nós entendemos os conceitos mais fundamentais da arbitragem triangular e começamos a olhar o mercado e montar as possibilidades de triangulação, apenas com o caminho Buy Buy Sell inicialmente.
Nesta segunda etapa, vamos gerar as hipóteses usando o caminho Buy Sell Sell e vamos monitorar os preços do mercado, a fim de ver quais hipóteses podem ser lucrativas, dando sequência ao projeto da parte 1.
Se preferir, pode assistir ao vídeo abaixo, tem o mesmo conteúdo.
Buy Sell Sell
Entendemos na parte 1 que um dos caminhos possíveis é fazer duas compras seguidas de uma venda. Vimos também que a segunda possibilidade é fazer uma compra seguida de duas vendas, como mostra o diagrama de exemplo abaixo.
Nós já temos todos os pares sendo negociados na corretora (variável allSymbols), temos todos os pares que podemos comprar em virtude do quote disponível (variável buySymbols) e um mapa de symbols que é o mesmo allSymbols, mas organizado com foco em performance de pesquisa.
Vamos criar no nosso index.js nossa função getBuySellSell, que é bem parecida com a getBuyBuySell anterior, mas com ajustes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function getBuySellSell(buySymbols, allSymbols, symbolsMap) { const buySellSell = []; for (let i = 0; i < buySymbols.length; i++) { const buy1 = buySymbols[i]; const right = allSymbols.filter(s => s.base === buy1.base && s.quote !== buy1.quote); for (let j = 0; j < right.length; j++) { const sell1 = right[j]; const sell2 = symbolsMap[sell1.quote + buy1.quote]; if (!sell2) continue; buySellSell.push({ buy1, sell1, sell2 }); } } return buySellSell; } |
Quase não tem nada novo a explicar aqui. O que muda basicamente é o filtro aplicado no allSymbols para procurar os candidatos a venda 1 e também a forma de procurar o candidato a venda 2. Mas se você comparar ambos com o diagrama de exemplo do BSS, verá que é somente isso que muda mesmo, em virtude da troca da segunda operação.
Agora, vamos usar esta função em nosso start para que tenhamos também toda a coleção de possibilidades BSS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
async function start() { //pega todas moedas que estão sendo negociadas console.log('Loading Exchange Info...'); const allSymbols = await exchangeInfo(); //moedas que você pode comprar const buySymbols = allSymbols.filter(s => s.quote === QUOTE); console.log('There are ' + buySymbols.length + " pairs that you can buy with " + QUOTE); //organiza em map para performance const symbolsMap = getSymbolMap(allSymbols); //descobre todos os pares que podem triangular BUY-BUY-SELL const buyBuySell = getBuyBuySell(buySymbols, allSymbols, symbolsMap); console.log('There are ' + buyBuySell.length + " pairs that we can do BBS"); //descobre todos os pares que podem triangular BUY-SELL-SELL const buySellSell = getBuySellSell(buySymbols, allSymbols, symbolsMap); console.log('There are ' + buySellSell.length + " pairs that we can do BSS"); } start(); |
Você deve esperar ver que ambos caminhos possuem a mesma quantidade de possibilidades. Isso não quer dizer que sejam iguais, mas em número de combinações sim. Na prática podem aparecer oportunidades em um caminho, em outro ou em nenhum, mas nunca nos dois, por isso que temos de testar ambos.
Monitorando os Preços
Agora que temos todas as possibilidades de triangulação temos de começar a nos preocupar com os preços. São os preços que nos dirão se cada modelo de triângulo é viável economicamente ou não, certo?
Para fazer o monitoramento de preços você pode fazer de maneira sync/pull ou de maneira async/push, sendo que o segundo cenário tem melhor performance e maior exatidão na informação. No entanto, para trabalhar com recebimento de preços (async/push) ao invés de consulta de preços (sync/pull), é necessário que a exchange tenha suporte a websockets/streams, coisa que a Binance felizmente tem.
Então, vamos criar um novo arquivo no nosso projeto chamado stream.js para criarmos esta conexão.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const WebSocket = require('ws'); const ws = new WebSocket("wss://stream.binance.com:9443/ws/!miniTicker@arr"); const BOOK = {}; ws.onmessage = async (event) => { const obj = JSON.parse(event.data); arr.map(obj => BOOK[obj.s] = { price: parseFloat(obj.c) } } function getBook(symbol){ return BOOK[symbol]; } module.exports = { getBook } |
Este módulo começa carregando o pacote ws que deixamos instalados ainda na parte 1 do projeto. Na sequência, usamos a classe WebSocket para nos conectarmos na stream de miniTicker, cuja documentação completa você confere aqui. Isso vai fazer com que o objeto ws passe a receber os preços de todos os pares sendo negociados na Binance, de segundo a segundo (nem todos pares vem todo segundo, mas somente aqueles cujo preço mudou).
Para processar esses dados chegando a gente cria uma função que recebe um evento. Esse evento tem os dados dos preços de cada moeda. Conforme esses dados vão sendo processados a gente guarda um resumo deles em um objeto BOOK, que expomos através de uma função getBook exportada. Usaremos esta função no arquivo principal do bot para que ele possa fazer as consultas de preço. Importe este módulo no início do index.js para que ele passe a receber os preços em background.
1 2 3 |
const stream = require("./stream"); |
Caso queira testar, pode colocar um console.log dentro da função do onmessage para imprimir o event.data e ver as informações chegando no console.
Viabilidade da Triangulação
Agora nós entramos na última e mais crucial parte do algoritmo que é a verificação de viabilidade financeira. Dentre todos os triângulos hipotéticos que podemos fazer, quais deles seriam lucrativos dados os preços atuais, o QUOTE que possuímos e a meta de PROFITABILITY que colocamos?
Para fazer isso usaremos duas fórmulas de cruzamento (cross-rate formulas), uma para as hipóteses BUY BUY SELL e outra para as hipóteses BUY SELL SELL, como mostra a imagem abaixo.
Essa fórmula deve ser aplicado a cada um dos triângulos hipotéticos que montamos e a função a seguir mostra como fazê-lo no caminho BBS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function processBuyBuySell(buyBuySell) { for (let i = 0; i < buyBuySell.length; i++) { const candidate = buyBuySell[i]; //verifica se já temos todos os preços let priceBuy1 = stream.getBook(candidate.buy1.symbol); if (!priceBuy1) continue; priceBuy1 = parseFloat(priceBuy1.price); let priceBuy2 = stream.getBook(candidate.buy2.symbol); if (!priceBuy2) continue; priceBuy2 = parseFloat(priceBuy2.price); let priceSell1 = stream.getBook(candidate.sell1.symbol); if (!priceSell1) continue; priceSell1 = parseFloat(priceSell1.price); //se tem o preço dos 3, pode analisar a lucratividade const crossRate = (1 / priceBuy1) * (1 / priceBuy2) * priceSell1; if (crossRate > PROFITABILITY) { console.log(`OP BBS EM ${candidate.buy1.symbol} > ${candidate.buy2.symbol} > ${candidate.sell1.symbol} = ${crossRate}`); console.log(`Investindo ${QUOTE}${AMOUNT}, retorna ${QUOTE}${((AMOUNT / priceBuy1) / priceBuy2) * priceSell1}`); } } } |
Começamos com um laço, que vai percorrer todas hipóteses. A cada hipótese, verificamos se já temos os preços dos três pares. Preços esses que consultamos usando a função getBook exportada no módulo stream.js. Se tivermos os 3 preços, aplicamos a fórmula de cross-rate e se o resultado for superior à PROFITABILITY configurada, então a oportunidade foi encontrada.
A título de didática, incluí uma mensagem a mais, indicando o retorno esperado na triangulação considerando o AMOUNT de QUOTE que você informou nas configurações. Futuramente, é aí que você deveria disparar as ordens reais de compra e de venda.
Agora vamos fazer a função que calcula a viabilidade para os cenários de BUY BUY SELL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function processBuySellSell(buySellSell) { for (let i = 0; i < buySellSell.length; i++) { const candidate = buySellSell[i]; //verifica se já temos todos os preços let priceBuy1 = stream.getBook(candidate.buy1.symbol); if (!priceBuy1) continue; priceBuy1 = parseFloat(priceBuy1.price); let priceSell1 = stream.getBook(candidate.sell1.symbol); if (!priceSell1) continue; priceSell1 = parseFloat(priceSell1.price); let priceSell2 = stream.getBook(candidate.sell2.symbol); if (!priceSell2) continue; priceSell2 = parseFloat(priceSell2.price); //se tem o preço dos 3, pode analisar a lucratividade const crossRate = (1 / priceBuy1) * priceSell1 * priceSell2; if (crossRate > PROFITABILITY) { console.log(`OP BSS EM ${candidate.buy1.symbol} > ${candidate.sell1.symbol} > ${candidate.sell2.symbol} = ${crossRate}`); console.log(`Investindo ${QUOTE}${AMOUNT}, retorna ${QUOTE}${((AMOUNT / priceBuy1) * priceSell1) * priceSell2}`); } } } |
Aqui é praticamente a mesma coisa que a função anterior, apenas mudando a fórmula de cross-rate.
Monitoramento Ininterrupto
Agora a última coisa que precisamos é de um mecanismo que monitore os triângulos ininterruptamente, para sempre que existir alguma oportunidade, que a gente fique sabendo e no futuro, talvez até negocie automaticamente.
Para isso podemos usar um setInterval, para que a cada x segundos a gente verifique os BBS e BSS novamente, deixando a nossa função start como abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
async function start() { //pega todas moedas que estão sendo negociadas console.log('Loading Exchange Info...'); const allSymbols = await exchangeInfo(); //moedas que você pode comprar const buySymbols = allSymbols.filter(s => s.quote === QUOTE); console.log('There are ' + buySymbols.length + " pairs that you can buy with " + QUOTE); //organiza em map para performance const symbolsMap = getSymbolMap(allSymbols); //descobre todos os pares que podem triangular BUY-BUY-SELL const buyBuySell = getBuyBuySell(buySymbols, allSymbols, symbolsMap); console.log('There are ' + buyBuySell.length + " pairs that we can do BBS"); //descobre todos os pares que podem triangular BUY-SELL-SELL const buySellSell = getBuySellSell(buySymbols, allSymbols, symbolsMap); console.log('There are ' + buySellSell.length + " pairs that we can do BSS"); setInterval(async () => { console.log(new Date()); processBuyBuySell(buyBuySell); processBuySellSell(buySellSell); }, INTERVAL) } |
O resultado será que o programa vai ficar imprimindo o horário do processamento e, eventualmente quando algum triângulo for viável, ele imprime as informações do triângulo e o suposto retorno esperado, como mostrado na imagem abaixo, onde defini o QUOTE como USDT, o AMOUNT como 10 e o PROFITABILITY como 1.003.
Espero que tenha gostado do tutorial e até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.
Procuro alguém para criar um algoritmo para arbitragem triangular bot direto na minha conta da binance
Infelizmente não desenvolvo projetos para terceiros, somente os meus robôs mesmo. Se quiser aprender a programar robôs, aí consigo ajudar com meus conteúdos e cursos.
Olé professor!
Seu código é extremamente rápido e bem escrito, parabéns!
Fiquei com uma dúvida com relação ao cálculo da viabilidade da lucratividade.
Considerando meu QUOTE em USDT, para fazer o cálculo, não seria necessário usar todos os valores envolvidos em USDT, ou seja, colocar os valores das moedas do triangulo convertidos em USDT para que eu possa fazer o cálculo correto? Do jeito que está o código, o cálculo está considerando valores em três moedas diferentes, o que, obviamente não vai dar uma lucratividade correta. Pode me dar um retorno sobre isso? Obrigado!
A análise financeira da arbitragem já está corretamente calculada fazendo as conversões necessárias com os dados de preço adequados. Como você suspeita que possa estar errada, o que vou recomendar é que pegue uma amostra (um triângulo e o preço dos três pares) e calcule manualmente no papel (fazendo as três operações, por exemplo BBS), talvez fazendo o passo a passo manualmente fique claro a matemática por trás dele e inclusive te permita definir os melhores parâmetros para sua versão.
Boa tarde! Lendo a documentação da Binance percebi que !miniTicker@arr não retorna o valor em tempo real, precisaria necessariamente que o valor da stream seja retornada em tempo real? ou pode ser com o resultado das últimas 24h da documentação https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-streams.md#all-market-mini-tickers-stream
Mini-ticker retorna a cada 1 segundo, o que costuma ser OK para a maioria dos cenários. Se quer resultado em tempo real pode usar a stream de book ticker, mas aí vai ter de conectar em até 1024 streams apenas, reduzindo as possibilidades de triangulação.
Vocês tem um tutorial de arbitragem de criptomoedas para corretoras descentralizadas como a uniswap?
Tenho tutorial para swap na Uniswap e PancakeSwap, pode usar eles como base para fazer o que deseja, mas não tenho tutorial ensinando arbitragem entre dex não.