Quando estamos programando scripts e bots para trading automatizado é natural que a quantidade de operações realizadas inviabilize o acompanhamento manual dos resultados da automação, se ela está dando lucro ou não. Claro, a gente bate o olho na carteira e sabe se ela está aumentando, mas quanto? Além disso, dependendo do volume mensal, é necessário o pagamento de imposto de renda, logo, quando os montantes começam a crescer, é imprescindível ter um controle mais apurado.
Para isso você pode exportar os dados de trades da Binance e consolidar em um Excel. Ou então, já que automatizou as operações, que tal automatizar também estes cálculos? No tutorial de hoje eu quero apresentar uma proposta de algoritmo que ajudará neste sentido. Ele baixará o seu histórico de ordens processadas e calculará o lucro (ou prejuízo) obtido no período.
Se preferir, pode assistir ao vídeo abaixo ao invés de ler, o conteúdo é o mesmo.
Vamos lá!
#1 – Setup do Ambiente
Vou partir do pressuposto aqui que você tem uma conta na Binance e que já fez trades nela (manual ou automatizado, não importa). Caso você seja cliente de outra corretora que também possua APIs, é possível fazer a adaptação do código abaixo, mas fica por sua conta. Recomendo inclusive que inicialmente você use o ambiente de testes durante a codificação, no vídeo abaixo ensino a criar chaves de teste (lá pela metade do vídeo).
Feito isso, antes de sairmos programando precisamos configurar nosso projeto. Usarei aqui a linguagem JavaScript, então precisamos do Node.js instalado na máquina. Se ainda não tem o ambiente de desenvolvimento para Node.js configurado, você pode ver como fazer no vídeo abaixo.
Agora crie uma pasta no seu computador com o nome de binance-calculator e dentro dela rode o comando abaixo pelo seu terminal de linha de comando para inicializar um projeto Node.js na pasta.
1 2 3 |
npm init -y |
Agora coloque um arquivo index.js vazio dentro da pasta e vamos instalar um pacote via NPM pra deixar nosso projeto preparado. Segue o comando de instalação:
1 2 3 |
npm install axios dotenv |
O módulo axios serve para fazer chamadas HTTP às APIs da corretora na sua aplicação Node.js, pois precisaremos de vários dados diferentes. Já o pacote DotEnv serve para carregar as configurações do projeto (chamadas de variáveis de ambiente). Para que o DotEnv funcione, você precisa criar um arquivo .env na raiz da sua aplicação (sem nome, apenas .env mesmo) com as seguintes variáveis dentro.
- SYMBOL=BTCUSDT (será o par de moedas que vamos calcular a lucratividade, em maiúsculas);
- BNB_COMMISSION=true (se você paga ou não as comissões dos trades usando BNB);
- START_TIME=0 (informe o timestamp do dia em que deseja passar a calcular ou 0 para calcular tudo);
- API_URL=https://api.binance.com/ (para produção) ou https://testnet.binance.vision (para dev/teste);
- API_KEY=sua acess key/chave de API na Binance;
- API_SECRET=seu secret key na Binance;
Na sequência um arquivo index.js na raiz da sua aplicação e para inicializar nosso projeto a partir dele, modifique o seu package.json para que no script de start ele execute:
1 2 3 4 5 |
"scripts": { "start": "node index" }, |
E com isso estamos com tudo preparado para codificação!
#2 – Obtendo os dados da ordens
Esse projeto vai ser uma aplicação console, precisamos carregar logo no início do index.js as dependências que vamos usar.
1 2 3 4 5 |
require("dotenv").config(); const axios = require('axios'); const crypto = require('crypto'); |
A primeira dependência é para carregar as variáveis do arquivo .env para a memória da aplicação, usaremos elas logo mais. A segunda dependência é o Axios, para fazer chamadas à API e a terceira será necessária para autenticação da chamada, é um pacote de criptografia nativo do Node.js e portanto não precisamos instalar ele.
Agora logo abaixo, vamos adicionar o carregamentoe. tratamento de algumas variáveis de ambiente, que eu explico na sequência.
1 2 3 4 5 6 7 8 |
const SYMBOL = process.env.SYMBOL; const COMMISSION = process.env.BNB_COMMISSION === "true" ? 0.075 : 0; const START_TIME = parseInt(process.env.START_TIME || 0); const API_KEY = process.env.API_KEY; const API_SECRET = process.env.API_SECRET; const API_URL = process.env.API_URL; |
Basicamente a ideia principal aqui é simplificar o acesso às configurações da aplicação, colocando-as em constantes. Apenas duas eu adicionei tratamento especial, a BNB_COMMISSION, que se estiver habilitada, define a comissão como 0.075% por trade. Caso contrário eu defino como 0, não porque seja gratuita, mas porque no caso de ordens pagando comissão na moeda que está sendo negociada o valor final executado já é líquido da comissão (-0.1%), ou seja, já foi aplicada. Já no caso do uso de BNB ela é cobrada à parte, por isso terei de calcular ela separadamente mais à frente para garantir que estou chegando no resultado de lucratividade real, considerando taxas.
E a outra variável tratada diferente é a START_TIME, que se não foi definida eu vou assumir 0. Essa variável define o timestamp mínimo de execução da ordem para ser incluída nos cálculos. Por exemplo, se você quer apenas as ordens executadas a partir de 01/02/2025 00:00 (UTC), então deveria adicionar o timestamp 1740787200000, mas se quiser que todas as ordens sejam incluídas, use 0.
Agora vamos para a obtenção das ordens em si, código da função getAllOrders abaixo, explicado na sequência.
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 |
async function getAllOrders() { const data = { symbol: SYMBOL, limit: 1000,//máximo permitido timestamp: Date.now(), recvWindow: 60000//máximo permitido, default 5000 }; const signature = crypto .createHmac('sha256', API_SECRET) .update(`${new URLSearchParams(data)}`) .digest('hex'); const newData = { ...data, signature }; const qs = `?${new URLSearchParams(newData)}`; try { const result = await axios({ method: 'GET', url: `${API_URL}/api/v3/allOrders${qs}`, headers: { 'X-MBX-APIKEY': API_KEY } }); console.log(result.data); return result.data; } catch (err) { console.error(err); } } getAllOrders(); |
No primeiro bloco de código estamos criando um objeto data com os parâmetros da requisição que faremos à Binance. Precisamos informar o symbol que queremos pegar as ordens, a quantidade de ordens (1000 é o máximo), o timestamp da sua máquina e a janela de tolerência de atraso na execução da consulta (60s é o máximo). Atenção ao relógio da sua máquina, é muito comum máquinas Windows estarem atrasadas ou adiantadas, mesmo que por segundos, o que causa rejeição da sua requisição no servidor da Binance. Neste outro artigo eu ensino como resolver problemas de timestamp (entre outros erros comuns).
No blog seguinte precisamos criar a assinatura digital da nossa requisição (signature). Para isso usamos o pacote crypto, os dados da requisição (data) e o API_SECRET, para garantir que é você mesmo quem está fazendo a requisição. Tudo isso seguindo o padrão HMAC-SHA256 definido pela própria Binance para segurança das assinaturas.
Com a assinatura pronta, hora de incluir ela junto aos demais dados da requisição (newData), montar uma querystring com tudo (qs) e então fazer a requisição de fato via Axios. Ela é um GET para o servidor da API_URL, mais especificamente para o endpoint allOrders documentado aqui. Para completar a requisição, incluímos um cabeçalho personalizado exigido pela corretora, informando nossa API_KEY.
O resultado da requisição será impresso no console e nada mais é do que um array de ordens da sua conta, como no exemplo abaixo (listei apenas uma no exemplo).
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 |
[{ symbol: 'BTCUSDT', orderId: 37259014423, orderListId: -1, clientOrderId: 'ios_81a74d876963474ea4938b06120284aa', price: '0.00000000', origQty: '0.00403000', executedQty: '0.00403000', cummulativeQuoteQty: '387.76152220', status: 'FILLED', timeInForce: 'GTC', type: 'MARKET', side: 'BUY', stopPrice: '0.00000000', icebergQty: '0.00000000', time: 1739806874047, updateTime: 1739806874047, isWorking: true, workingTime: 1739806874047, origQuoteOrderQty: '388.56646380', selfTradePreventionMode: 'EXPIRE_MAKER' } ] |
Caso não tenha nenhuma ordem realizada ainda, não se preocupe, vou te ensinar como mockar uma massa de dados para facilitar o seus testes mais tarde.

#3 – Calculando a lucratividade
Agora que você já tem a função getAllOrders funcionando, remova o console.log dela pois não é mais necessário e também a chamada dela no final do arquivo. Vamos criar agora outra função, a calcResults, como abaixo, sendo chamada logo na sequência. Basicamente precisamos agrupar as compras, depois as vendas e fazer os cálculos em cima dos montantes apurados.
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 |
async function calcResults() { const allOrders = await getAllOrders(); const allBuyOrders = allOrders.filter(order => order.side === "BUY" && order.status === "FILLED" && order.workingTime > START_TIME); const totalBuyQuantity = allBuyOrders.map(order => parseFloat(order.executedQty)).reduce((a, b) => a + b); const totalBuyQuote = allBuyOrders.map(order => parseFloat(order.cummulativeQuoteQty)).reduce((a, b) => a + b); console.log(`Total Spent (${SYMBOL}): ${totalBuyQuote.toFixed(2)}`); console.log(`Number of Buys: ${allBuyOrders.length}`); const avgBuyPrice = totalBuyQuote / totalBuyQuantity; console.log(`Avg Buy Price (${SYMBOL}): ${avgBuyPrice.toFixed(2)}`); const allSellOrders = allOrders.filter(order => order.side === "SELL" && order.status === "FILLED" && order.workingTime > START_TIME); const totalSellQuantity = allSellOrders.map(order => parseFloat(order.executedQty)).reduce((a, b) => a + b); const totalSellQuote = allSellOrders.map(order => parseFloat(order.cummulativeQuoteQty)).reduce((a, b) => a + b); console.log(`Total Received (${SYMBOL}): ${totalSellQuote.toFixed(2)}`); console.log(`Number of Sells: ${allSellOrders.length}`); const avgSellPrice = totalSellQuote / totalSellQuantity; console.log(`Avg Sell Price (${SYMBOL}): ${avgSellPrice.toFixed(2)}`); const totalCommission = (allSellOrders.length + allBuyOrders.length) * COMMISSION; const quotePnL = (totalSellQuote - totalBuyQuote) * ((100 - totalCommission)/100); console.log(`Quote PnL (${SYMBOL}): ${quotePnL.toFixed(2)}`); const pnl = quotePnL * 100 / totalBuyQuote; console.log(`PnL (%): ${pnl.toFixed(2)}%`); } calcResults(); |
No primeiro bloco, nós pegamos todas ordens chamando a função anteriormente criada.
No segundo bloco, fazemos todos os cálculos ligados às ordens de compras nessa ordem:
- dentre todas ordens, filtramos apenas as BUY com status FILLED e que tenham sido executadas dentro do período estipulado (allBuyOrders);
- fazemos um somatório da quantidade de base asset (moeda à esquerda do par) adquirida em todas compras (totalBuyQuantity);
- fazemos um somatório da quantidade de quote asset (moeda à direita do par) gasta em todas compras (totalBuyQuote);
- imprimimos no console algumas dessas informações;
- calculamos o preço médio de compra (avgBuyPrice) e imprimimos ele;
No terceiro bloco nós fazemos praticamente a mesma coisa, porém considerando as ordens SELL (venda):
- dentre todas ordens, filtramos apenas as SELL com status FILLED e que tenham sido executadas dentro do período estipulado (allSellOrders);
- fazemos um somatório da quantidade de base asset (moeda à esquerda do par) vendida em todas vendas (totalSellQuantity);
- fazemos um somatório da quantidade de quote asset (moeda à direita do par) recebida em todas vendas (totalSellQuote);
- imprimimos no console algumas dessas informações;
- calculamos o preço médio de venda (avgSellPrice) e imprimimos ele;
Com todos esses montantes apurados, nós temos de calcular agora a lucratividade. Para isso precisamos primeiro entender se devemos ou não descontar as comissões da Binance. Se você usa BNB para pagar as comissões, eu calculo o percentual a ser descontado do lucro líquido. Caso não use BNB, o valor executado nos seus trades já inclui o desconto da comissão.
Na linha seguinte, calculamos o PnL (“profits and losses” ou “lucros e perdas”) na moeda de cotação (direita do par), fazendo uma regra de três simples. Depois calculamos o PnL em percentual, comparando o PnL de cotação com o valor investidor nas compras. Por fim, apenas imprimimos os valores e chamamos a função na execução do programa.
Para testar, rode o programa novamente e, se houver ordens executadas na sua conta, o resultado irá aparecer.
No exemplo acima, com dados fictícios, a lucratividade no período foi de 70% ou USDT 4658,70 a mais do que investi comprando BTC. Últimas dicas abaixo, incluindo como testar mais facilmente e como evoluir ainda mais o projeto.
#4 – Pontos adicionais
Pode ser que você não tenha dados de trades suficientes para testar o seu algoritmo ou então como saber se ele calculou tudo corretamente? Para isso eu vou te fornecer abaixo uma massa de dados que lhe permitirá chegar exatamente no resultado que mostrei na imagem acima, assim conseguirá ter certeza que programou tudo certo. Claro, você pode mudar o que ensinei também, falarei mais disso após essa última dica técnica.
Crie um arquivo mock.json na raiz da sua aplicação e coloque nele o seguinte conteúdo (esta é a massa de dados que usei no print anterior, coloquei apenas os campos mais importantes).
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
[ { "symbol":"BTCUSDT", "orderId":25542659033, "executedQty":"0.05021000", "cummulativeQuoteQty":"3616.12420000", "status":"FILLED", "type":"MARKET", "side":"SELL", "workingTime":1710167753527 }, { "symbol":"BTCUSDT", "orderId":25667154166, "executedQty":"0.06907000", "cummulativeQuoteQty":"4640.81399070", "status":"FILLED", "type":"MARKET", "side":"SELL", "workingTime":1710502288429 }, { "symbol":"BTCUSDT", "orderId":26617893454, "executedQty":"0.03126000", "cummulativeQuoteQty":"1907.35078200", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1713382192896 }, { "symbol":"BTCUSDT", "orderId":26678210882, "executedQty":"0.03128000", "cummulativeQuoteQty":"2030.23622000", "status":"FILLED", "type":"MARKET", "side":"SELL", "workingTime":1713528384524 }, { "symbol":"BTCUSDT", "orderId":28064125850, "executedQty":"0.01899000", "cummulativeQuoteQty":"1169.78418990", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1719237641185 }, { "symbol":"BTCUSDT", "orderId":35416324911, "executedQty":"0.00952000", "cummulativeQuoteQty":"999.66378400", "status":"FILLED", "type":"MARKET", "side":"SELL", "workingTime":1737149208089 }, { "symbol":"BTCUSDT", "orderId":36037108947, "executedQty":"0.01052000", "cummulativeQuoteQty":"1040.31543600", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1737978998518 }, { "symbol":"BTCUSDT", "orderId":37101401631, "executedQty":"0.01279000", "cummulativeQuoteQty":"1235.24451470", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1739423871524 }, { "symbol":"BTCUSDT", "orderId":37123656157, "executedQty":"0.00224000", "cummulativeQuoteQty":"214.92800000", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1739458964317 }, { "symbol":"BTCUSDT", "orderId":37148513046, "executedQty":"0.00223000", "cummulativeQuoteQty":"215.15033310", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1739498692297 }, { "symbol":"BTCUSDT", "orderId":37201889596, "executedQty":"0.00425000", "cummulativeQuoteQty":"415.29546500", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1739624990400 }, { "symbol":"BTCUSDT", "orderId":37259014423, "executedQty":"0.00403000", "cummulativeQuoteQty":"387.76152220", "status":"FILLED", "type":"MARKET", "side":"BUY", "workingTime":1739806874047 } ] |
Com esse arquivo mock.json pronto, vá na função getAllOrders, comente o conteúdo dela e coloque esse no lugar.
1 2 3 4 5 6 7 |
async function getAllOrders() { const mock = require("./mock.json"); return Promise.resolve(mock); //o código antigo comentado vai aqui } |
Dessa forma, a função vai carregar as ordens de exemplo que estão no nosso arquivo e retornar eles como se tivesse sido feita uma consulta na API. Bacana, não?
Para finalizar, vou recomendar algumas evoluções que você pode fazer no seu projeto.
Primeiro, pode ser interessante trazer todos os valores para sua moeda fiduciária (BRL no Brasil), assim você conseguirá depois calcular o PnL total de todos os pares negociados ou até mesmo calcular a necessidade ou não de pagar Imposto de Renda (volumes acima de R$35k exigem pagamento de 15% sobre o lucro, na data que escrevo este artigo). Para te ajudar nessa conversão, recomendo a leitura e prática deste artigo (inclui vídeo).
Segundo, se você opera vários pares de moeda e quer que seu script calcule sobre todos eles, pode ser legal transformar a variável SYMBOL em SYMBOLS e iterar sobre este array. Para pegar todos os pares existentes na Binance você pode usar este tutorial (inclui vídeo), depois pode filtrar apenas pelos que você costuma operar (todos terminados em USDT, por exemplo). No entanto, tome cuidado pois a API /api/v3/allorders consome 20 do limite de requisições que você tem direito, então dê uma pausa entre cada chamada para evitar problemas. Querendo saber mais sobre limites de uso das APIs, consulte este artigo (inclui vídeo).
Até a próxima!

Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.