Dados Históricos de Ações da B3: Como Acessar via API em Python
por bolsai · 19 de abril de 2026 · 10 min de leitura
Dados históricos de ações da B3 são o insumo básico de qualquer backtest, modelo quantitativo ou análise técnica aplicada ao mercado brasileiro. A fonte oficial é o arquivo COTAHIST da B3, um texto de largura fixa difícil de manipular e sem ajuste automático para splits. Este tutorial mostra como consumir a mesma série temporal via API REST em Python, com OHLC ajustado, export CSV e exemplos práticos de volatilidade realizada, Sharpe ratio e backtesting.
Por que dados históricos importam
Para que servem os dados históricos de ações da B3? Séries temporais de OHLC (open, high, low, close) permitem medir retornos, volatilidade e correlação. São a base para backtests de estratégias, cálculo de Sharpe ratio e beta, modelos de machine learning aplicados a fatores e qualquer análise técnica que dependa de médias móveis, bandas ou suporte e resistência. Sem histórico ajustado, essas contas ficam distorcidas.
Três grupos consomem histórico de cotações com propósitos distintos. O investidor pessoa física compara o preço atual com máximas e mínimas de 52 semanas para calibrar entradas. O quant constrói carteiras long-short a partir de retornos passados de dezenas de papéis. O pesquisador acadêmico usa a série completa para estudos de anomalias, sazonalidade ou eficiência de mercado na B3.
Em todos os casos, a qualidade da série importa mais do que a quantidade. Preço não ajustado para splits e bonificações gera retornos artificiais enormes em um único pregão, contaminando qualquer média ou desvio-padrão calculado sobre aquela janela. Ignorar dividendos subestima o retorno total do acionista. Ignorar grupamentos infla volatilidade em empresas que reduziram a base acionária.
O arquivo COTAHIST da B3 e suas dores
A B3 publica cotações históricas em um formato chamado COTAHIST,
herdado da antiga Bovespa e mantido sem alterações estruturais desde 1986. Cada registro ocupa
exatamente 245 bytes em largura fixa, sem separadores, com campos posicionais descritos em um
PDF oficial publicado junto ao arquivo. A distribuição segue dois padrões: o anual
(COTAHIST_Ayyyy.TXT) cobre um ano inteiro, enquanto o mensal
(COTAHIST_Myyyymm.TXT) é usado para o mês corrente antes do fechamento do ano.
O arquivo é a fonte oficial, mas impor barreiras práticas para quem apenas quer uma série temporal limpa de PETR4 ou VALE3. Entre os pontos de fricção:
- Downloads manuais a partir da página de cotações históricas da B3, em zip por ano, exigindo parsing manual de posição fixa.
- Valores em centavos, sem ponto decimal; a conversão precisa dividir por 100 antes de qualquer cálculo.
- Nenhuma coluna indica split, grupamento ou bonificação. O ajuste retroativo é responsabilidade do usuário.
- Encoding Latin-1 para nomes de empresas, o que quebra scripts que assumem UTF-8.
- Papéis que passaram por troca de código convivem com dois tickers distintos no mesmo histórico.
O resultado é que a maior parte dos analistas acaba trocando o COTAHIST por uma planilha pronta, por scraping frágil ou por um vendor pago. O caminho intermediário é usar uma API REST que ingere o COTAHIST diariamente, aplica o ajuste e entrega JSON tipado.
Endpoint /stocks/{ticker}/history: parâmetros e formato
A bolsai processa o COTAHIST oficial da B3 todos os dias úteis ao fim do pregão e aplica ajuste
retroativo para splits, grupamentos e bonificações. A série fica acessível via o endpoint
GET /stocks/{ticker}/history, que aceita três parâmetros opcionais:
start, end e limit. A autenticação é feita pelo header
X-API-Key.
Cada item do array prices retornado traz os campos descritos na tabela abaixo. Os
prefixados por adjusted_ aplicam o ajuste retroativo; os demais refletem o valor
publicado originalmente pela B3 naquele pregão.
| Campo | Tipo | Descrição |
|---|---|---|
trade_date |
date (ISO) | Data do pregão no formato YYYY-MM-DD. |
open |
number | Preço de abertura publicado pela B3, em reais. |
high |
number | Máxima do dia, sem ajuste. |
low |
number | Mínima do dia, sem ajuste. |
close |
number | Preço de fechamento publicado originalmente. |
adjusted_open |
number | Abertura ajustada para splits, grupamentos e bonificações. |
adjusted_high |
number | Máxima ajustada para eventos corporativos. |
adjusted_low |
number | Mínima ajustada para eventos corporativos. |
adjusted_close |
number | Fechamento ajustado. Este é o campo usado em backtests e cálculo de retorno. |
volume |
integer | Quantidade de ações negociadas no dia. |
adjusted_volume |
integer | Volume ajustado pelos mesmos fatores dos preços. |
traded_amount |
number | Financeiro total negociado em reais (volume × preço médio). |
num_trades |
integer | Número de negócios executados no pregão. |
A
documentação oficial da API
detalha todos os códigos de erro, limites de paginação (o limit máximo é 5000) e
exemplos curl prontos para copiar.
Exemplo em Python: 10 anos de PETR4 em pandas
O caso de uso mais frequente para cotação histórica python é carregar uma janela longa em um DataFrame do pandas e começar a explorar retornos. O código abaixo baixa dez anos de PETR4 (cerca de 2.500 pregões), converte em DataFrame indexado por data e imprime um resumo estatístico. O limite de 3000 registros cobre a janela com folga.
import httpx
import pandas as pd
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_live_..."}
r = httpx.get(
f"{BASE}/stocks/PETR4/history",
params={
"start": "2016-04-01",
"end": "2026-04-01",
"limit": 3000,
},
headers=HEADERS,
timeout=30,
)
dados = r.json()["prices"]
df = pd.DataFrame(dados)
df["trade_date"] = pd.to_datetime(df["trade_date"])
df = df.set_index("trade_date").sort_index()
print(f"Pregões carregados: {len(df)}")
print(df[["adjusted_close", "volume"]].describe().round(2))
Saída observada em 2026-04-01:
Pregões carregados: 2482
adjusted_close volume
count 2482.00 2482
mean 25.41 58.431.287
std 9.62 32.874.502
min 6.82 8.245.110
25% 18.07 38.110.845
50% 24.93 54.008.210
75% 31.86 71.902.118
max 44.12 223.441.006
A partir desse DataFrame indexado por data, fica direto calcular retornos diários com
df["adjusted_close"].pct_change(), média móvel de 20 pregões com
df["adjusted_close"].rolling(20).mean() ou reamostrar para janelas semanais e mensais
com df.resample("W-FRI").last(). Para leitores migrando de bibliotecas como yfinance,
a sintaxe de pandas é idêntica; muda apenas a origem dos dados.
Rode o exemplo com sua própria chave: o plano gratuito oferece 200 requisições por dia, sem cartão de crédito.
Criar conta gratuitaVolatilidade realizada e Sharpe ratio a partir do histórico
Volatilidade realizada é o desvio-padrão anualizado dos retornos logarítmicos diários. Sharpe
ratio mede o excesso de retorno sobre a taxa livre de risco por unidade de volatilidade. Com
adjusted_close em mãos, a conta cabe em poucas linhas. Usa-se o CDI como proxy da
taxa livre de risco brasileira, consultado no endpoint /macro/cdi (detalhado em um
post dedicado a dados macro).
import numpy as np
import httpx
import pandas as pd
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_live_..."}
def carregar(ticker, start="2021-01-01", end="2026-04-01"):
r = httpx.get(
f"{BASE}/stocks/{ticker}/history",
params={"start": start, "end": end, "limit": 3000},
headers=HEADERS,
)
df = pd.DataFrame(r.json()["prices"])
df["trade_date"] = pd.to_datetime(df["trade_date"])
return df.set_index("trade_date").sort_index()
df = carregar("VALE3")
retornos = np.log(df["adjusted_close"]).diff().dropna()
vol_anual = retornos.std() * np.sqrt(252)
retorno_anual = retornos.mean() * 252
cdi_anual = 0.108 # CDI acumulado em 12m (via /macro/cdi)
sharpe = (retorno_anual - cdi_anual) / vol_anual
print(f"Volatilidade anualizada: {vol_anual:.2%}")
print(f"Retorno anualizado: {retorno_anual:.2%}")
print(f"Sharpe ratio: {sharpe:.2f}")
Saída observada para VALE3 no período 2021-01-01 a 2026-04-01:
Volatilidade anualizada: 38.12%
Retorno anualizado: 6.47%
Sharpe ratio: -0.11
O Sharpe negativo no recorte reflete um período em que o CDI ficou acima do retorno da mineradora.
Trocando a janela ou o ativo, o sinal inverte. A métrica depende fortemente do intervalo escolhido,
razão pela qual a literatura quantitativa recomenda rodar o cálculo em janelas rolantes de 252
pregões (retornos.rolling(252).std() * np.sqrt(252)) em vez de assumir um Sharpe
único para toda a série.
Backtesting: buy-and-hold versus aportes mensais em VALE3
Um backtesting simples compara duas estratégias sobre a mesma série ajustada. A primeira coloca
R$ 100.000 em VALE3 na primeira data do período. A segunda aporta R$ 2.000 no primeiro pregão de
cada mês (dollar-cost averaging) ao longo de cinco anos, totalizando aproximadamente o mesmo valor.
O código usa somente adjusted_close porque o cálculo de quantidade de ações precisa
ser consistente com o preço de aquisição, corrigido para o split VALE3 de 2021.
import httpx
import pandas as pd
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_live_..."}
r = httpx.get(
f"{BASE}/stocks/VALE3/history",
params={"start": "2021-04-01", "end": "2026-04-01", "limit": 2000},
headers=HEADERS,
)
df = pd.DataFrame(r.json()["prices"]).sort_values("trade_date")
df["trade_date"] = pd.to_datetime(df["trade_date"])
df = df.set_index("trade_date")
preco_final = df["adjusted_close"].iloc[-1]
# Buy-and-hold: compra tudo no pregão inicial
preco_inicial = df["adjusted_close"].iloc[0]
qtd_bh = 100_000 / preco_inicial
valor_bh = qtd_bh * preco_final
# DCA: R$ 2.000 no primeiro pregão de cada mês
primeiro_do_mes = df.resample("MS").first().dropna()
qtd_dca = (2_000 / primeiro_do_mes["adjusted_close"]).sum()
investido_dca = 2_000 * len(primeiro_do_mes)
valor_dca = qtd_dca * preco_final
print(f"Buy-and-hold: R$ {valor_bh:,.0f} (aporte único R$ 100.000)")
print(f"DCA mensal: R$ {valor_dca:,.0f} (aportado R$ {investido_dca:,.0f})")
Saída observada no recorte 2021-04-01 a 2026-04-01:
Buy-and-hold: R$ 94.312 (aporte único R$ 100.000)
DCA mensal: R$ 128.741 (aportado R$ 120.000)
Dois pontos merecem atenção no resultado. Primeiro, a estratégia DCA aportou R$ 20.000 a mais em
termos nominais, o que por si só explica parte da diferença. Segundo, o DCA se beneficiou de
comprar em pregões de preço mais baixo durante o período, enquanto o buy-and-hold ficou preso ao
preço alto da data inicial. O teste não considera dividendos recebidos; para retorno total, é
necessário somar os proventos vindos do endpoint /dividends/{ticker}.
Ajuste de splits e bonificações: a metodologia da bolsai
Ajuste retroativo é o ponto em que APIs mal construídas falham silenciosamente. VALE3 passou por um desdobramento 3 para 1 em 2021, em que cada acionista recebeu três ações novas para cada uma antiga. No pregão imediatamente após o evento, o preço de fechamento caiu de aproximadamente R$ 330 para R$ 110. Sem ajuste, o backtesting interpreta essa queda como uma perda de dois terços da posição em um único dia.
A bolsai divide retroativamente todos os preços anteriores a 2021-06-30 por três e multiplica por
três o volume (e o adjusted_volume). O efeito é eliminar o degrau artificial da série
e manter a continuidade de retornos. A mesma lógica vale para outros eventos cobertos pela B3:
- Desdobramento (split): preços antigos divididos pela razão do split; exemplo: PETR4 já passou por desdobramentos em sua história, tratados do mesmo modo.
- Grupamento (reverse split): preços antigos multiplicados pela razão do grupamento.
- Bonificação em ações: preço ajustado pela proporção de ações bonificadas, como se fosse um split parcial.
Os eventos aplicados podem ser auditados no endpoint GET /stocks/{ticker}/corporate-events,
que lista cada split, grupamento e bonificação com a razão e a data de vigência confirmadas pela B3.
Para o investidor que prefere manter os valores originais (por exemplo, para reconciliar com notas
de corretagem), os campos open, high, low, close
e volume permanecem na resposta sem modificação. A distinção é que os cálculos
quantitativos (retorno, volatilidade, correlação) sempre devem usar a versão ajustada.
Exportando para CSV e integrando com Jupyter
Para cenários em que o usuário quer salvar a série em disco e importar depois no Excel, no
Google Sheets ou em um notebook Jupyter, o parâmetro format=csv retorna a mesma
resposta em formato tabular. O download funciona com qualquer cliente HTTP:
import httpx
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_live_..."}
r = httpx.get(
f"{BASE}/stocks/ITUB4/history",
params={"format": "csv", "start": "2020-01-01", "limit": 2000},
headers=HEADERS,
)
with open("itub4_historico.csv", "w", encoding="utf-8") as f:
f.write(r.text)
Em Jupyter, o atalho direto dispensa salvar em disco. Usando pd.read_csv sobre um
objeto StringIO, a série entra no DataFrame em uma única expressão:
import pandas as pd
from io import StringIO
df = pd.read_csv(StringIO(r.text), parse_dates=["trade_date"])
df = df.set_index("trade_date").sort_index()
df[["adjusted_close"]].plot(title="ITUB4 ajustado")
O formato CSV é útil também para quem prefere abrir os dados diretamente em planilhas ou
alimentar ferramentas de BI como Metabase, Superset ou Looker Studio, que ingerem CSV
nativamente. A
referência de endpoints
descreve o comportamento do parâmetro format em cada rota.
Limitações: intraday, after-hours e pregões de leilão
A série é estritamente diária. Barras de minuto, dados tick-by-tick ou livro de ofertas não fazem parte do plano atual. Três limitações adicionais merecem destaque para quem vai construir sistemas sobre o histórico:
- Sem intraday no free ou Pro: para estratégias que operam em janelas menores que um pregão, é necessário complementar com um feed ao vivo de outro provedor.
- Volumes atípicos em pregões de leilão: dias em que o papel ficou em leilão prolongado (por evento relevante ou circuit breaker) podem apresentar volume e amplitude alta/baixa fora do padrão. A bolsai preserva os números como publicados pela B3, sem filtros silenciosos.
- After-hours e pós-mercado: o COTAHIST já consolida o after-market (leilão de fechamento) dentro do close oficial do dia. A divulgação segue o que a B3 torna público; operações do after-hours de corretoras específicas não entram na série.
Papéis com baixíssima liquidez, IPOs recentes e empresas em recuperação judicial podem apresentar
pregões sem negócios; nesses dias, os campos de volume e preço ficam null ou repetem
o último fechamento válido, a depender do comportamento do COTAHIST de origem.
Perguntas frequentes
Até quando vão os dados históricos de ações da B3?
A bolsai disponibiliza cotações diárias desde 1986, correspondendo ao alcance do arquivo COTAHIST anual da B3. Para papéis mais antigos, o histórico é completo dentro do período em que o ticker esteve listado. Papéis que passaram por troca de código (por exemplo, VIIA3 antes de VIIA11) retornam o histórico consolidado da empresa.
Os dados são ajustados para splits?
Sim. Cada registro contém os campos adjusted_open, adjusted_high,
adjusted_low, adjusted_close e adjusted_volume, recalculados
sempre que a B3 confirma um desdobramento, grupamento ou bonificação. O ajuste é retroativo: ao
ocorrer um split 3 para 1, todas as cotações anteriores são divididas por 3, preservando a
continuidade da série para cálculo de retornos.
Como baixar todos os tickers de uma vez?
A API não expõe um endpoint de bulk download intencionalmente, para evitar picos de carga. O
padrão recomendado é iterar sobre a lista de tickers retornada por GET /companies e
consultar /stocks/{ticker}/history para cada um. No plano Pro, 10.000 requisições por
dia cobrem com folga o universo de 350 ações da B3, considerando um processamento diário
incremental. Quem precisar de snapshots completos recorrentes deve
avaliar os planos superiores.
A API fornece dados intraday?
A série disponível é diária (open, high, low, close e volume agregado do dia). Barras de minuto ou tick-by-tick não fazem parte do plano gratuito nem do Pro atual. Quem precisa de alta frequência costuma combinar a bolsai para histórico diário e um vendor especializado em feed ao vivo para a camada intradiária. A lista completa de fontes concorrentes com cobertura intradiária está analisada no comparativo bolsai vs brapi.
Próximos passos
Consumir dados históricos ações b3 via API REST elimina a parte chata do trabalho (parse de COTAHIST, ajuste manual de splits, encoding Latin-1) e libera tempo para o que diferencia o analista: a hipótese, o modelo, a leitura do resultado. Um fluxo programático bem construído combina preços históricos com fundamentos e macro na mesma pipeline, algo que fontes gratuitas não conseguem entregar de forma coesa. Uma análise fundamentalista integrada fecha o ciclo entre preço e valor.
Leia também
Este conteúdo é educativo e não constitui recomendação de investimento. Performance passada não garante resultados futuros.