A maioria das estratégias de Wealthtech quebra no primeiro trimestre de produção porque o backtest ignorou a realidade operacional da B3. Ver "o gráfico subir" não é suficiente; precisamos saber se a estratégia sobrevive à taxa de custódia, ao emolumento da bolsa e, principalmente, ao desvio de alocação entre os rebalanceamentos. Como editor de RegTech, já vi algoritmos prometendo alpha mirabolante que simplesmente desconsideraram que o Ibovespa mudou de metodologia em 2024, criando viés de sobrevivência nos dados.
O objetivo aqui não é entregar um script "copiar e colar" para brincar de investidor, mas construir a base de uma engenharia de validação. Vamos simular uma carteira multiativo simples, composta por BOVA11 (Ações), HGLG11 (Fundos Imobiliários) e LFTs (Renda Fixa atrelada à Selic), aplicando uma regra de rebalanceamento baseada em desvio de peso, e não em calendário fixo. Isso reduz a necessidade de rotação da carteira e, consequuentemente, os custos.
Para este exercício, assumo que você está rodando Python 3.12+ e entende o básico de séries temporais.
A preparação do ambiente e a seleção de dados
O erro clássico é baixar dados de fontes não ajustadas. Na B3, proventos de FIIs e ações alteram o preço spot. Se você não trabalha com preços ajustados, seu backtest vai achar que o ativo despencou no dia ex-dividendos, gerando sinais de compra falsos.
Evite usar APIs gratuitas genéricas que não tratam eventos corporativos brasileiros corretamente. Para este exemplo, usaremos a biblioteca yfinance como proxy, mas em produção严肃 (sério) eu recomendo extrair os arquivos de histórico da B3 ou contratar um provedor de dados local. Nosso foco será a lógica, não a ingestão.
Instale as dependências necessárias:
pip install pandas numpy matplotlib yfinance
Vamos definir o período de análise. Escolhi uma janela de 5 anos, de janeiro de 2021 a janeiro de 2026, para capturar cenários de alta volatilidade, ciclos de alta de juros e a recuperação pós-crise.
import yfinance as yf
import pandas as pd
import numpy as np
# Tickers e seus pesos ideais (Target Allocation)
tickers = ['BOVA11.SA', 'HGLG11.SA', 'SELIC']
# Nota: SELIC será simulado via taxa livre de risco, pois não há ticker direto no yfinance
target_weights = {'BOVA11.SA': 0.40, 'HGLG11.SA': 0.30, 'SELIC': 0.30}
initial_capital = 100000 # R$ 100.000,00
# Download de dados
start_date = '2021-01-01'
end_date = '2026-01-01'
data = yf.download(tickers[:2], start=start_date, end=end_date)['Adj Close']
Observe que excluí o 'SELIC' do download. Para a renda fixa pós-fixada, precisamos simular o retorno diário baseado na meta Selic do período. Em 2026, com a volatilidade da taxa básica, aproximar a Selic pela média do período é um erro, mas para fins didáticos de estrutura de código, vamos usar uma curva constante de 10,75% a.a. convertida para diária.
Definindo a lógica de gatilho (Trigger)
Rebalancear todo dia 20 de cada mês é o jeito preguiçoso e caro de operar. A melhor abordagem quantitativa é o rebalanceamento por "bandas de tolerância" ou drift. Você só mexe na posição se ela estourar um limite percentual em relação ao peso ideal.
Definiremos um limite de desvio de 5% absolutos. Ou seja, se a alocação da BOVA11 subir de 40% para 45% ou cair para 35%, acionamos o rebalanceamento.
def check_rebalance(current_weights, target_weights, threshold=0.05):
"""
Verifica se algum ativo desviou além do limite de tolerância.
Retorna True se for necessário rebalancear.
"""
for asset in target_weights:
drift = abs(current_weights[asset] - target_weights[asset])
if drift > threshold:
return True
return False
Essa lógica economiza muito em custos de transação. Em anos de tendência forte, como 2024/2025, a carteira pode ficar meses sem precisar de ajuste, permitindo que os "ganhadores corram". Quando você olha para custos de transação, usar algoritmos de execução inteligentes se torna vital, como discutimos na análise entre Algoritmo TWAP x VWAP: Qual minimiza mais o custo de transação na liquidação de grandes ordens?.
O loop de backtest: onde a mágica (e o erro) acontece
A maioria dos scripts de backtest usa resampling (reagregação) mensal. Isso esconde os riscos intramonth. Para validar realmente, precisamos iterar dia a dia (dia útil, no caso). Vamos manter um registro do saldo em caixa e da quantidade de ativos.

O código abaixo simula a operação de compra e venda. É crucial descontar custos aqui. Na B3, a conta não barata: negociação (0.20% a 0.30% dependendo do volume), emolumentos (cerca de 0.005% sobre o volume) e liquidação (0.02%). Vamos chutar um custo total redutor de 0.3% sobre cada operação de rebalanceamento.
# Inicialização
portfolio_value = pd.Series(index=data.index, dtype=float)
cash = initial_capital
holdings = {ticker: 0 for ticker in tickers[:2]} # Quantidade de papéis
selic_daily_rate = (1 + 0.1075) ** (1/252) - 1 # Taxa diária aproximada
for date in data.index:
# 1. Atualizar valor dos ativos
total_asset_value = 0
prices = {}
for ticker in tickers[:2]:
price = data.loc[date, ticker]
prices[ticker] = price
total_asset_value += holdings[ticker] * price
# Adicionar renda fixa (caixa acumulado com Selic)
cash = cash * (1 + selic_daily_rate)
current_total_value = total_asset_value + cash
portfolio_value[date] = current_total_value
# 2. Calcular pesos atuais
current_weights = {}
for ticker in tickers[:2]:
current_weights[ticker] = (holdings[ticker] * prices[ticker]) / current_total_value
current_weights['SELIC'] = cash / current_total_value
# 3. Verificar gatilho
if check_rebalance(current_weights, target_weights, threshold=0.05):
# Calcular valores ideais para hoje
ideal_values = {k: current_total_value * v for k, v in target_weights.items()}
# Executar trades (Simplificado: market order no close do dia)
# Ajustar Ações e FIIs
for ticker in tickers[:2]:
current_val = holdings[ticker] * prices[ticker]
diff = ideal_values[ticker] - current_val
# Se diff > 0, compra. Se diff < 0, vende.
# Aplicando custo de transação
trade_value = diff * (1 if diff > 0 else 0.997) # 0.3% de custo na venda/ajuste
amount_to_trade = trade_value / prices[ticker]
holdings[ticker] += amount_to_trade
cash -= trade_value
# Ajustar Caixa (o que sobrou/efaltou fica na Renda Fixa)
# Ajuste fino para garantir soma = 100%
# (Num backtest real, usamos um solver, aqui vamos simplificar a alocação de caixa)
pass # O caixa já absorve o residual nas operações acima
Esse loop tem um detalhe sutil que representa a vida real: quando vendemos um ativo para comprar outro, sofremos com o bid-ask spread (não modelado aqui) e com os custos fixos. Se o seu rebalanceamento gera ganhos de R$ 50,00 e o custo da operação é R$ 80,00, você destruiu riqueza. Estratégias de alocação baseadas puramente em CVaR ou Mean-Variance muitas vezes ignoram que a implementação consome o prêmio de risco.
O pesadelo dos pontos cegos de alocação
Ao rodar o código acima, você vai notar algo curioso: o portfólio tende a perder valor nos meses de alta volatilidade se a banda de tolerância for muito apertada. O "churn" (rotação de carteira) fica alto. É exatamente esse tipo de comportamento que 3 pontos cegos de Robo-advisors que quebram a alocação de risco em crises repentinas alerta. Muitas plataformas automatizadas rebalanceiam demais para "parecerem ativas", corroendo o patrimônio do cliente com taxas.
No mercado brasileiro, especificamente com FIIs, temos outro problema: o impacto da amortização de JCP. Se você usar preços fechados sem ajuste, o backtest vai mostrar uma queda artificial.
Análise dos Resultados e Métricas Reais
Ter o DataFrame portfolio_value é o começo. Plotar um gráfico de linha bonito não valida a estratégia. Precisamos comparar.
Calcule o retorno acumulado e compare com um benchmark "Buy and Hold" da mesma alocação inicial, mas sem rebalanceamento.
buy_and_hold_value = initial_capital * (1 + data.pct_change().fillna(0)).add(1).cumprod().mean(axis=1) * 1 + \
(initial_capital * 0.30) * ((1 + selic_daily_rate) ** np.arange(len(data))) # Simplificado
# Cálculo do Drawdown Maximo
running_max = portfolio_value.cummax()
drawdown = (portfolio_value - running_max) / running_max
max_drawdown = drawdown.min()
print(f"Retorno Backtest: {(portfolio_value.iloc[-1]/initial_capital - 1)*100:.2f}%")
print(f"Máx. Drawdown: {max_drawdown*100:.2f}%")
Se o seu rebalanceamento teve um retorno menor que o Buy & Hold, mas um Maximum Drawdown significativamente menor (digamos, -15% contra -25%), a estratégia pode ter mérito em termos de conservadorismo. Contudo, em 2025/2026, vimos que estratégias passivas de baixo custo venceram ativos ativos na maioria dos horizontes acima de 3 anos. Se o seu backtest não mostrar um Sharpe Ratio acima de 1.0 para justificar o custo de gestão, o algoritmo precisa voltar para a prancheta.
Validação final e passos para produção
O código apresentado é uma simulação "ingênua". Para levar isso para produção em 2026, você precisará considerar:
- Liquidação D+1 e D+0: A B3 mudou ciclos de liquidação. Seu código assume execução imediata. Na vida real, o dinheiro da venda só entra no dia seguinte, o que impede a compra no mesmo dia se não houver caixa pré-alocado.
- Fracionários: O backtest permite comprar 0.0001 ação. Na realidade, há lotes padrão (100) ou fracionários mínimos (1). Isso gera "erro de arredondamento" que impacta carteiras pequenas (até R$ 50 mil).
- Slippage de execução: Assumir que você compra no preço de fechamento é otimista. Você deve descontar um spread médio do ativo (para BOVA11 é baixo, para small caps é alto).
Implementar essa camada de realidade é o que separa um hobbyista de uma fintech séria. Eu recomendo rodar esse script com dados de "tick-by-tick" (intraday) para os últimos 6 meses, validando se os sinais de rebalanceamento realmente seriam executáveis dentro dos limites de caixa da conta.
Uma vez validado o risco, o passo seguinte é a automação dos pagamentos e reinvestimentos. Algumas arquiteturas modernas já utilizam blockchain para liquidar dividendos instantaneamente, eliminando o risco contraparte da câmara de compensação. Como vimos em um caso recente, automatizamos o reinvestimento de dividendos via smart contract e reduzimos a ineficiência fiscal em 0,5%, algo que o seu backtest provavelmente ignorou até agora.
Se o seu resultado mostra consistência após ajustar os custos para o mercado real, você tem algo nas mãos. Se não, descarte. Em finanças quantitativas, um backtest bonito é a regra; um modelo lucrativo é a exceção.