Atenção: com a compra pela BitPreço, as APIs da Biscoint foram desabilitadas, conforme anunciado aqui. Novo vídeo ensinando bot de arbitragem neste link.
Recentemente eu publiquei um tutorial aqui no blog falando sobre a Biscoint, um comparador de preço de criptomoedas. Isso por si só já é um serviço bem bacana e útil e eles possuem uma API que ajuda a consumir a informação deles e monitorar o mercado e é isso que ensinei na parte 1 deste tutorial.
No entanto, a parte mais legal eu acabei não contando na ocasião: eles possuem um serviço chamado Biscoint Buy, que permite fazer arbitragem de criptomoedas negociando dentro das exchanges que eles possuem integração. Ou seja, ao invés de apenas você monitorar qual a exchange que está com o BTC mais barato ou caro do mercado, você pode realizar a compra ou venda, conforme sua estratégia.
E esse é justamente o mote da parte 2: vou te ensinar como de fato fazer a compra e venda de criptomoedas em diferentes exchanges usando o serviço Biscoint Buy.
Importante salientar, no entanto, que o Biscoint Buy é um serviço recente e limitado. Ele não te dá muita liberdade de escolha, principalmente entendendo como ele funciona por trás: a Biscoint mantém contas nas exchanges com fundos suficientes para intermediar as compras/vendas para os usuários. Obviamente eles não conseguem ter fundos infinitos em todas elas, então além da questão do preço em si, internamente a Biscoint faz a gestão das carteiras para que as ofertas possam ser realizadas, o que infelizmente não deixa muito flexível pra gente na ponta.
Em resumo, você não vai poder escolher a exchange de onde quer comprar/vender a criptomoeda. Você vai escolher comprar ou vender e a Biscoint vai te oferecer o melhor preço que ela pode fechar negócio em seu nome.
Dito isso, vamos ao tutorial.
Setup da Parte 2
Antes de conseguirmos realizar operações de fato precisamos de duas coisas: a primeira é saber se temos saldo e a segunda é que precisamos descobrir como fazer chamadas autenticadas à API da Biscoint, já que até agora só tínhamos usado os endpoints públicos.
A parte boa é que podemos resolver estes dois problemas de uma vez só, criando uma função para obter saldo e já implementado para ela e futuras funções o suporte a chamadas assinadas.
Sendo assim, vamos voltar ao projeto da lição anterior e instalar duas novas dependências que vamos precisar.
1 2 3 |
npm i crypto-js dotenv |
O pacote dotenv é para podermos configurar as nossas chaves na aplicação. Fazer isso é bem simples, primeiro crie um arquivo .env na raiz da sua aplicação com o seguinte conteúdo, preenchendo as variáveis com os valores que obteve ao criar as chaves de API na parte 1 deste tutorial.
1 2 3 4 |
API_KEY=<SUA API KEY> API_SECRET=<SUA API SECRET> |
Agora para que este arquivo seja carregado automaticamente na inicialização do nosso projeto, modifique o script de start no seu package.json para incluir o carregamento do dotenv.
1 2 3 4 5 |
"scripts": { "start": "node -r dotenv/config index" }, |
E para garantir que tudo ficou funcionando como deveria, inclua este comando no topo do seu index.js.
1 2 3 4 5 6 7 8 9 |
const apiKeyCheck = process.env.API_KEY; const apiSecretCheck = process.env.API_SECRET; if(!apiKeyCheck || !apiSecretCheck) { console.log(`Crendentials not found!`); process.exit(0); } |
Assim, se você se esquecer de preencher algum dos dois, terá um erro.
Mas agora voltando a falar do segundo pacote que instalamos, o crypto-js, ele serve para atividades de criptografia, como a assinatura de requisições como as que vamos ter de fazer a seguir.
Chamadas Autenticadas
Obter o preço de uma moeda é bem simples, já que é uma informação pública, certo? Agora saber o saldo da carteira de alguém não é tão simples assim, exige que o seu bot prove que ele tem autorização para isso. Ele faz isso usando suas chaves de API para assinar as requisições. Além disso, existem algumas regras adicionais que a Biscoint exige para essas requisições.
Sendo assim, vamos ajustar a nossa função call do módulo biscoint.js para incluir estas regras adicionais, sem quebrar o funcionamento antigo.
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 29 30 31 32 33 |
async function call(endpoint, params, method = 'GET') { const headers = { "Content-Type": "application/json" }; const url = `https://api.biscoint.io/v1/${endpoint}`; let data = undefined; if (method === 'POST') { params = params || {}; data = JSON.stringify(params, Object.keys(params).sort()); const nonce = `${Date.now() * 1000}`; const signedParams = sign(endpoint, nonce, data); headers["BSCNT-NONCE"] = nonce; headers["BSCNT-APIKEY"] = `${process.env.API_KEY}`; headers["BSCNT-SIGN"] = signedParams; data = JSON.parse(data); } const config = { url, method, headers, data, timeout: 5000 } const result = await axios(config); return result.data; } |
Aqui as nossas alterações já começam na assinatura da função, que recebeu alguns parâmetros a mais, indicando os dados a serem enviados na requisição (usaremos mais tarde para definir o quanto queremos comprar, por exemplo) e o método HTTP que vamos usar, sendo que as requisições autenticadas são sempre POST ao invés do GET que estávamos usando.
Uma vez dentro da função a novidade é justamente o if em cima do method recebido. Se for um POST é porque queremos fazer uma requisição autenticada, logo temos passos adicionais como a construção de um objeto JSON com os dados da requisição (quando houverem), a geração de um nonce (que deve ser uma string numérica única e crescente para cada requisição), os parâmetros assinados e alguns cabeçalhos especiais.
Para a assinatura dos parâmetros vamos precisar de uma função adicional, chamada sign, que você deve incluir no mesmo módulo.
1 2 3 4 5 6 7 8 |
function sign(endpoint, nonce, data) { const strToBeSigned = `v1/${endpoint}${nonce}${data}`; const hashBuffer = Buffer.from(strToBeSigned).toString("base64"); return createHmac(hashBuffer, process.env.API_SECRET).toString(); } |
Esta função recebe o endpoint que vai ser chamado, o nonce e os dados da requisição. Ele transforma isso tudo em um buffer de bytes e usa uma função de criptografia que recebe o buffer e o API_SECRET por parâmetro para gerar uma assinatura digital chamada HMAC.
Esse createHmac é uma função do pacote crypto-js então você vai ter que fazer um require no topo do módulo biscoint.js, como segue.
1 2 3 |
const createHmac = require("crypto-js/hmac-sha384"); |
Certo, agora que temos a nossa assinatura sendo gerada corretamente de acordo com o endpoint, parâmetros e secret key, vamos falar dos cabeçalhos adicionais que precisam ser passados quando é uma requisição privada.
- BSCNT-NONCE: a string nonce criada anteriormente, a mesma que usamos na assinatura;
- BSCNT-APIKEY: a API Key que você criou na parte 1 do tutorial;
- BSCNT-SIGN: a assinatura digital HMAC criada no passo anterior;
Com esses três cabeçalhos devidamente preenchidos sua request está pronta para ser enviada.
Obtendo o Saldo
Agora podemos usar esta versão 2.0 da função call para fazer chamadas autenticadas, como a de saldo. Para fazer isso, vamos criar uma função balance no seu módulo biscoint.js que vai ser bem simples, como segue.
1 2 3 4 5 |
function balance() { return call("balance", null, "POST"); } |
E agora em nosso index.js vamos fazer a lógica para chamar esta função e armazenar o resultado.
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 29 30 31 32 |
let BRL, BTC; const biscoint = require("./biscoint"); function percent(value1, value2) { return (Number(value2) / Number(value1) - 1) * 100; } async function loadBalance(){ console.log(`Loadig balance...`) const result = await biscoint.balance(); BRL = result.data.BRL; BTC = result.data.BTC; console.log(`BRL: ${BRL}`); console.log(`BTC: ${BTC}`); } async function doCycle() { try { if (!BRL) { await loadBalance(); } } catch (err) { console.error(err.response ? err.response.data : err.message); } } setInterval(doCycle, 5010); doCycle(); |
Aqui repare que aproveitei a estrutura da parte 1, que executa uma função a cada 5010ms. Como não precisamos atualizar o saldo nesse período sempre, eu testo para ver se temos o saldo em BRL. Se não tivermos, a gente chama a API para pegar e guarda em uma variável.
Aproveitei e peguei o saldo de BTC também, pode ser útil mais tarde.
Agora nosso próximo passo e fazer as compras e vendas usando o serviço da Biscoint Pay.
Fazendo Ofertas
Diferente de operações de compra e venda tradicionais, onde comprar barato é a regra número um para que seja possível vender mais caro algum tempo depois, em operações de arbitragem o que importa mesmo é a possibilidade de comprar e vender no mesmo momento aproveitando apenas a diferença de cotação.
Resumindo: eu só posso comprar se já tenho como vender AGORA por um preço maior.
Note então que o risco é maior do que em operações convencionais, motivo pelo qual eu não recomendo que seja a sua estratégia principal, até porque as janelas de oportunidade são raras e curtas também, diferente do mercado tradicional de cripto que tem MUITAS oportunidades ao longo do dia em MUITAS criptomoedas diferentes.
Outro ponto importante é entender como a dinâmica de compra e venda do Biscoint Buy funciona. Ao invés de você fazer uma compra a mercado ou a limite como em uma corretora tradicional, no Biscoint Buy você solicita uma oferta com melhor preço e se gostar do que está disponível, você efetiva esta oferta.
Então nosso primeiro passo para fazer a arbitragem acontecer de fato é obter estas ofertas.
Para fazer isso, vamos criar uma função nova no biscoint.js para obtenção de ofertas.
1 2 3 4 5 6 |
function offer(amount, op, base = "BTC") { const isQuote = op === 'buy' ? true : false; return call("offer", { amount, op, isQuote, base, quote: "BRL" }, "POST"); } |
Aqui nós temos uma função que serve tanto para obter ofertas de compra quanto de venda, bastando passar os parâmetros adequados. O primeiro parâmetro é a quantidade de R$ que vamos gastar (se for compra) ou a quantidade de cripto que queremos gastar (se for venda), deve ser uma string. O segundo parâmetro é a operação (buy ou sell, em minúsculo) e o último, opcional, é a cripto que vamos negociar, BTC por padrão.
Internamente usamos de lógica para entender se o amount é quote asset (BRL) ou não e depois chamamos a função call que vai enviar nossos parâmetros e assinar a requisição para a Biscoint.
Agora, podemos chamar essa função no index.js para pegar uma oferta de compra.
1 2 3 4 5 |
console.log('Getting a Buy Offer'); const result = await biscoint.offer(BRL, 'buy'); console.log(result.data); |
Repare que passei a variável BRL por parâmetro, que é tudo que tenho de Reais na minha carteira. O resultado traz várias informações úteis da oferta.
Em baseAmount você tem o tanto de BTC que dá pra comprar com o saldo de BRL. Em efPrice você tem o preço da cotação e em expiresAt você tem o tempo limite para confirmar sua ordem, que falaremos mais à frente, usando o campo offerId.
Agora podemos chamar duas vezes e verificar a possibilidade de arbitragem.
Realizando a Arbitragem
O primeiro passo para verificar a viabilidade de fazer arbitragem é obter duas ofertas: uma de compra e outra de venda.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
console.log('Getting a Buy Offer'); const buyOffer = await biscoint.offer(BRL, 'buy'); console.log('Getting a Sell Offer'); const sellOffer = await biscoint.offer(buyOffer.data.baseAmount, 'sell'); console.log('Buy Price: ' + buyOffer.data.efPrice); console.log('Sell Price: ' + sellOffer.data.efPrice); const gap = percent(buyOffer.data.efPrice, sellOffer.data.efPrice); console.log(`%: ${gap.toFixed(2)}`); |
Com as duas ofertas em mão, podemos comparar seus preços e entender o gap que tem entre os preços para ver a lucratividade ou prejuízo se a operação fosse realizada. Eu não vou entrar aqui em estratégia, mas lhe adianto que mesmo que o gap seja positivo (ou seja, que o preço de venda esteja superior ao preço de compra), a lucratividade apresentada será a MÁXIMA. Atenção à palavra MÁXIMA pois não há garantias de que você consiga toda essa lucratividade pois o mercado é muito volátil e assim como você, outros devs estão de olho em arbitragem também, é tudo muito rápido.
Uma vez que você identifique a oportunidade e ela seja vantajosa, é hora de confirmar as operações. Para isso, vamos criar uma última função no módulo biscoint.js para confirmação.
1 2 3 4 5 |
function confirmOffer(offerId) { return call("offer/confirm", { offerId }, "POST"); } |
E vamos chamar esta função no index.js, de acordo com a sua estratégia (coloquei um exemplo bobo ali em cima do gap percentual).
1 2 3 4 5 6 7 8 |
if (gap > 1) { const buyResult = await biscoint.confirmOffer(buyOffer.data.offerId); const sellResult = await biscoint.confirmOffer(sellOffer.data.offerId); console.log(sellResult); loadBalance(); } |
No código acima, quando o gap está acima de 1% eu confirmo as operações de compra e venda, imprimindo o resultado da venda e carregando o saldo atualizado na memória.
Agora basta implementar a sua estratégia no código e colocar o seu bot de arbitragem a rodar para ter ele monitorando oportunidades e fazendo entradas e saídas relâmpago para ganhar com arbitragem de moedas.
Qualquer dúvida que tenha ficado, deixe nos comentários e se quiser fazer o download dos fontes, use o form logo mais abaixo.
Até mais!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.