Tutorial 19 abr 2026 10 min de leitura

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:

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 gratuita

Volatilidade 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:

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:

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.