Criei um algoritmo para balancear minha carteira de ações
Recentemente, venho me aprofundando no mercado de ações, e o que mais tenho buscado é manter uma carteira equilibrada, por diversos fatores. Basicamente, defino uma porcentagem ideal para cada ticker e, todo mês, faço aportes seguindo essas proporções. Não sigo essas porcentagens cegamente, elas servem mais como um norte a ser seguido.
🧩 O Problema
Antes, eu mantinha todas as porcentagens em uma planilha no Excel. Todo mês eu acessava a planilha, atualizava os valores de cada ação e depois ia comprando uma por uma, para calcular o quanto precisava comprar de cada uma.
Apesar de não ser algo muito demorado, era um processo chato. E eu me perguntei: por que gastar 30 minutos todo mês se posso gastar alguns dias automatizando isso? haha
🛠 A Solução
Optei por usar Go, pois é uma linguagem que venho estudando ultimamente e também por ser uma ótima opção para CLIs (Command-Line Interfaces). Queria algo simples, local e prático, sem necessidade de subir um site ou aplicação web.
O projeto pode ser dividido em três partes:
- Leitura da carteira
- Atualização dos valores das ações
- Recomendação de compras para rebalancear
1. Leitura da carteira
Para facilitar, criei um arquivo JSON com os dados das ações, ao invés de integrar diretamente com a B3 ou APIs mais complexas. O formato é o seguinte:
{
"stocks": [
{
"ticker": "BBAS3",
"ideal_ratio": 0.5,
"current_price": 27.78,
"amount": 1,
"updated_at": "2025-02-07T21:35:24.02599019-03:00"
},
{
"ticker": "BBDC4",
"ideal_ratio": 0.5,
"current_price": 11.99,
"amount": 1,
"updated_at": "2025-02-07T21:35:24.257580504-03:00"
}
]
}
As informações mais importantes são: ticker
, ideal_ratio
(porcentagem ideal) e amount
(quantidade). As demais são geradas automaticamente pelo sistema.
Futuramente, pretendo utilizar um banco de dados como SQLite, e já estruturei o projeto com repository pattern
, então será fácil fazer essa mudança.
2. Atualização dos valores
Utilizei a API da BRAPI (https://brapi.dev/) para atualizar os preços das ações. Ela possui um plano gratuito que atende bem às minhas necessidades.
O processo é simples:
- Leio os dados do JSON.
- Faço as requisições para atualizar os preços.
- Uso o campo
updated_at
para evitar excesso de chamadas à API, só atualizo após um intervalo de 2 horas.
Esse intervalo pode ser removido/modificado diretamente no código: stock.go#L26.
3. Algoritmo de Rebalanceamento
Essa foi a parte mais dificil. Refiz o algoritmo diversas vezes até chegar a algo funcional. O que eu precisava era:
- Manter as porcentagens o mais próximas possível das ideais.
- Evitar comprar ações que já ultrapassaram sua meta.
- Diversificar ao máximo, evitando gastar tudo em uma única ação.
- Gastar todo o dinheiro disponível, otimizando o aporte. Ou seja, eu precisava de um algoritmo guloso, que tentasse comprar o máximo de tickers diferentes, desde que possível.
Etapas do algoritmo:
- Cálculo do patrimônio total após o novo aporte (somando o valor atual da carteira com o novo investimento).
- Com esse novo valor, é possível calcular a porcentagem atual de cada ação na carteira.
- A partir disso, verificamos quanto falta para balancear cada ticker.
Mas isso não é suficiente. Por exemplo, se por exemplo,
PETR4
deveria representar 50% e atualmente tem 25%, o algoritmo não pode simplesmente alocar os 25% restantes nela, ignorando o restante do portfólio.
Por isso, implementei a lógica de "rodadas de compra":
for remaining > 0 {
// Continue comprando...
}
Priorização das ações
A cada rodada, é criada uma lista de prioridades com base no "desvio" entre o valor ideal e o atual. O rank de cada ação é calculado assim:
newValue := currentValue + stock.CurrentPrice
deviationBefore := math.Abs(idealValue - currentValue)
deviationAfter := math.Abs(idealValue - newValue)
improvement = (deviationBefore - deviationAfter) / stock.CurrentPrice
improvement = improvement * (1.0 / (1.0 + float64(currentShares)))
- Se o desvio aumenta após a compra, o rank será negativo, ou seja, essa ação será despriorizada.
- Também penalizamos ações que já foram compradas várias vezes, para evitar compras repetidas.
Ordenando as ações
Depois de calcular os ranks, ordenamos a lista:
func (s *PortfolioService) sortPriorities(priorities []stockPriority) []stockPriority {
sort.Slice(priorities, func(i, j int) bool {
if priorities[i].rank == priorities[j].rank {
return priorities[i].stock.CurrentPrice < priorities[j].stock.CurrentPrice
}
return priorities[i].rank > priorities[j].rank
})
return priorities
}
Se duas ações tiverem o mesmo rank, a prioridade será dada à mais barata.
💸Hora da compra
A parte final é a mais simples: comprar.
Basta percorrer a lista de prioridades e, caso o rank
seja positivo e ainda exista saldo, realizamos a compra.
for remaining > 0 {
priorities := s.makePriorityList(stocks, buys, idealValues, remaining)
priorities = s.sortPriorities(priorities)
// Hora de comprar
bought := false
for _, priority := range priorities {
if priority.rank > 0 && priority.stock.CurrentPrice <= remaining {
buys[priority.stock.Ticker]++
remaining -= priority.stock.CurrentPrice
bought = true
break
}
}
if !bought {
break
}
}
A cada compra, atualizamos o saldo e seguimos até que não seja mais possível comprar nenhuma ação.
O projeto está aberto no github: https://github.com/xoesae/stock-balancer/tree/main. Ainda estou aprendendo Go, sugestões de melhoria e performance são sempre bem vindos! O que acharam? usariam meu CLI no seu dia-a-dia?
Legal seu projeto.
Edit: sei q o foco não é sobre o q irei dizer, mas resolvi escrever pq imagino q vc terá o msm problema.
Eu venho pensando em como criar uma carteira minha própria para o gerenciamento do jeito q quero. E olhando o q vc fez, acho q de todo seu projeto, vc tem tem a msm falha q eu estou tendo: Como pegar os dados da sua carteira de corretoras/b3 automaticamente. Eu já dei uma breve olhada em Open Finance, dei uma olhada em APIs de alguns bancos, mas ainda é difícil obter esses dados. Ou eu ainda não achei.
Pra gente q desenvolve, criar algoritmos é a etapa mais fácil, mas ter acesso aos dados reais q é a dor de cabeça. Ai q entra preencher da forma manual. Vc buscou por usar json como sua atual base de dados, mas vc sabe como é chato preencher esses dados. Nos testes q fazia eu fiz desse jeito, até usei csv, mas foi um pé no saco manter.
Claro q imagino q futuramente seu software terá um CRUD para preencher um banco de dados, mas enquanto isso não acontece, se quiser uma dica inicial, veja como recuperar os dados do google sheets e transformar esses dados em json pra assim vc usar no seu projeto (use da estrutura do repository para isso). Preencher dados em uma tabela pronta é mais fácil q preencher um json. E vc pode até simular um banco de dados lá, claro q manualmente, afinal google sheets é um monte de tabelas.