Tutorial 14 jul 2026 11 min de leitura por bolsai

Como Migrar do yfinance para uma API da B3: Substituir Ações Brasileiras Sem Dados Vazios (2026)

Quem trabalha com ações brasileiras em Python já viu yf.download("PETR4.SA") retornar um DataFrame vazio ou cheio de NaN, com a mensagem "No data found, symbol may be delisted". O yfinance é ótimo para prototipar com dados globais, mas a B3 expõe problemas específicos: o sufixo .SA falha de forma intermitente, tickers mudam por incorporação e grupamento, e não há fundamentos da CVM. Este guia mostra como migrar para uma API oficial, com código antes e depois, mapeamento de Adj Close e resolução de ticker-drift.

TL;DR

O yfinance é scraping não oficial do Yahoo Finance: as ações da B3 chegam via sufixo .SA e sofrem com respostas vazias e NaN, ausência de fundamentos da CVM e ticker-drift que quebra o pipeline em silêncio quando uma empresa é renomeada ou incorporada.

A bolsai é uma API REST com fonte oficial da B3 e da CVM: preços ajustados por splits, grupamentos e dividendos desde 1986, mais 27 indicadores fundamentalistas calculados, dividendos, FIIs e macro. Autenticação por header X-API-Key, plano gratuito de 200 requisições/dia sem cartão.

A migração é direta: trocar yf.download por um GET em /stocks/{ticker}/history, mapear Adj Close para adjusted_close e resolver tickers antigos via /companies.

Por que o yfinance falha com ações brasileiras

O yfinance não é uma API oficial. Ele lê endpoints internos do Yahoo Finance que nunca foram documentados como produto público. Para ações dos Estados Unidos, essa base costuma ser densa o bastante para funcionar. Para a B3, a cobertura é secundária, e é aí que os problemas aparecem. Entender a causa de cada falha ajuda a decidir o que precisa mudar no código ao migrar.

O sufixo .SA e os dados vazios ou NaN

No Yahoo, ações da B3 usam o sufixo .SA: PETR4 vira PETR4.SA, VALE3 vira VALE3.SA. Esquecer o sufixo garante um retorno vazio, mas colocá-lo também não garante dados. É comum yf.download("PETR4.SA") devolver um DataFrame vazio, uma coluna inteira de NaN ou a mensagem "No data found, symbol may be delisted" mesmo com o papel ativo e líquido. As causas variam: rate limit do Yahoo, mudança de endpoint interno, cobertura incompleta do papel ou simples instabilidade. Não há contrato: o mesmo script pode rodar hoje e falhar amanhã sem nenhuma alteração no seu código.

Tickers delistados e ticker-drift

Empresas mudam de nome, são incorporadas ou passam por grupamento, e o ticker acompanha. Em novembro de 2025, a Eletrobras virou Axia Energia e as ordinárias deixaram de ser negociadas como ELET3 para virar AXIA3 na B3. A Via (antiga Via Varejo) já tinha passado de VVAR3 para VIIA3. Quando isso acontece, o Yahoo pode aposentar o símbolo antigo e o histórico some ou fica truncado. O sintoma mais perigoso não é o erro visível: é o pipeline que continua rodando com uma série cortada na data da mudança, sem levantar exceção. O backtest roda, o número sai, e ele está errado.

O que falta: fundamentos, DY calculado e FIIs

Mesmo quando o preço vem, falta o resto. O yfinance expõe alguns campos fundamentalistas via Ticker(...).info, mas para papéis brasileiros esses campos vêm frequentemente vazios, defasados ou inconsistentes, porque dependem da cobertura do Yahoo e não da CVM. Não há histórico trimestral de balanço padronizado, não há dividend yield calculado sobre janela definida, e FIIs, que dependem de P/VP e DY, ficam praticamente descobertos. Para análise fundamentalista séria da B3, o preço é só o começo do que se precisa.

yahooquery e outras alternativas trocam um problema por outro

A reação natural é buscar outra biblioteca que leia o mesmo Yahoo: yahooquery, pandas-datareader e afins. O problema é que a fonte continua sendo a mesma: um endpoint não oficial, sujeito às mesmas quebras, mesmos NaN, mesmo ticker-drift. Trocar de wrapper não resolve a raiz. O que muda o jogo é trocar a fonte por uma API oficial da B3 e da CVM, com contrato estável e dados ajustados. O restante deste guia mostra como fazer isso sem reescrever o projeto inteiro.

Migração linha a linha: yf.download para requisição bolsai

A migração mais comum é substituir a chamada de histórico de preços. O yfinance retorna um DataFrame com colunas Open, High, Low, Close, Adj Close e Volume. A bolsai retorna um JSON com uma lista prices, cada item trazendo os mesmos campos em snake_case mais os ajustados. O bloco abaixo mostra o antes e o depois, buscando o histórico de PETR4.

Antes — yfinance
import yfinance as yf

# B3 exige o sufixo .SA
df = yf.download(
    "PETR4.SA",
    start="2024-01-01",
)

# Pode voltar vazio ou com NaN
# sem lançar exceção.
print(df["Adj Close"].tail())
Depois — bolsai
import httpx

API_KEY = "sua_chave_aqui"
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": API_KEY}

r = httpx.get(
    f"{BASE}/stocks/PETR4/history",
    headers=HEADERS,
    params={"start": "2024-01-01"},
)
prices = r.json()["prices"]
# Sem sufixo .SA, contrato estável.

Sem sufixo .SA, sem adivinhar se o Yahoo cobre o papel. O ticker vai direto na URL, a autenticação é por header e a resposta segue um contrato documentado em /docs. Para reproduzir o formato que o yfinance entrega, basta carregar a lista prices num DataFrame e indexar por trade_date.

Crie uma conta gratuita e rode a primeira requisição em minutos: 200 requisições por dia, sem cartão de crédito.

Criar conta gratuita

Cotação e histórico com o histórico de preços

Para a última cotação isolada, existe /stocks/{ticker}/quote, que retorna close, volume e trade_date do último pregão. No mundo MCP, essa mesma consulta é a ferramenta get_stock_quote, e o histórico completo é get_price_history. O exemplo abaixo carrega o histórico direto num DataFrame do pandas, no formato equivalente ao do yfinance.

import httpx
import pandas as pd

API_KEY = "sua_chave_aqui"
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": API_KEY}

r = httpx.get(
    f"{BASE}/stocks/PETR4/history",
    headers=HEADERS,
    params={"start": "2024-01-01", "limit": 500},
)
df = pd.DataFrame(r.json()["prices"])
df["trade_date"] = pd.to_datetime(df["trade_date"])
df = df.set_index("trade_date").sort_index()

print(df["adjusted_close"].tail(3))
# trade_date
# 2026-07-09    38.12
# 2026-07-10    38.44
# 2026-07-11    38.31

Preços ajustados por proventos

Este é o ponto onde a maioria dos backtests silenciosamente erra. O Adj Close do yfinance ajusta a série por dividendos e desdobramentos, e é ele que deve ser usado para retorno total, não o Close. Na bolsai, o campo equivalente é adjusted_close. Cada registro do histórico traz open/high/low/close (o preço nominal do pregão) e a versão ajustada adjusted_open/high/low/close, já corrigida por splits, grupamentos e dividendos. O mapeamento é direto:

yfinance (coluna) bolsai (campo) Uso
Open / High / Low / Closeopen / high / low / closePreço nominal do pregão
Adj Closeadjusted_closeRetorno total, backtest
VolumevolumeVolume negociado
(não separado)adjusted_open / high / lowOHLC ajustado completo
(index)trade_dateData do pregão

Para calcular retorno diário no backtest, use df["adjusted_close"].pct_change(). Esse detalhe conecta com o guia de histórico de preços ajustados da B3, que detalha como splits e grupamentos entram na conta.

Fundamentos com o endpoint de fundamentos

O maior ganho da migração não é o preço, é o que o yfinance não entrega. O endpoint /fundamentals/{ticker} (a ferramenta get_fundamentals no MCP) retorna um objeto plano com 27 indicadores calculados a partir dos dados da CVM: pl, pvp, roe, ev_ebitda, margens, endividamento e mais. Nada de campos vazios ou inconsistentes.

r = httpx.get(
    f"{BASE}/fundamentals/PETR4",
    headers=HEADERS,
)
d = r.json()

print(d["pl"], d["pvp"], d["roe"])
# 5.32 1.42 26.6

Para comparar vários papéis de uma vez existe a ferramenta compare_stocks, e para filtrar o universo por múltiplos há screen_stocks. O dividend yield, que o yfinance calcula de forma opaca, vem pré-calculado em /dividends/{ticker} (ferramenta get_dividends), no campo dividend_yield_ttm. A metodologia de cada indicador está documentada, o que importa para quem precisa reproduzir números. Uma cotação de ações em Python e um bloco de fundamentos cabem no mesmo cliente HTTP.

Lidando com ticker-drift e ativos delistados

O ponto mais frágil de qualquer pipeline baseado em yfinance é a suposição de que um ticker é eterno. Ele não é. Quando uma empresa é renomeada, incorporada ou faz grupamento, o código de negociação muda, e a série presa ao símbolo antigo se rompe. A bolsai resolve isso de duas formas: resolução de ticker e histórico contínuo da empresa.

Resolução de ticker com busca de empresas

Quando um script antigo aponta para ELET3 e não encontra dados recentes, o endpoint /companies (a ferramenta search_companies) permite buscar pela razão social ou nome de pregão e descobrir o ticker atual. Para varrer setores inteiros e mapear o universo, existe /companies/sectors (ferramenta list_sectors), que lista os setores válidos com a contagem de empresas ativas.

r = httpx.get(
    f"{BASE}/companies",
    headers=HEADERS,
    params={"search": "axia", "limit": 3},
)

for c in r.json()["data"]:
    print(c["ticker_primary"], c["trade_name"], c["status"])
# AXIA3  AXIA ENERGIA  ATIVO

Séries de empresas incorporadas

Descoberto o ticker atual, o histórico de preços da bolsai devolve a série completa da empresa desde 1986, mesmo para papéis que já mudaram de código. Consultar um ticker antigo como VIIA3 em /stocks/{ticker}/history retorna todo o histórico da empresa, sem o corte que o Yahoo aplicaria ao aposentar o símbolo. Para o backtest, isso significa uma série contínua e ajustada, não um gráfico que morre na data da renomeação. É a diferença entre um resultado confiável e um resultado que parece certo mas está truncado.

Tabela: yfinance vs API da B3 (bolsai)

A tabela abaixo resume as diferenças que motivam a migração, do ponto de vista de quem desenvolve para a B3 em Python.

Critério yfinance bolsai (API da B3)
Fonte dos dadosYahoo Finance (não oficial)B3 e CVM (oficial)
Cobertura da B3Secundária, irregularDedicada
Sufixo .SAObrigatório, falha comumNão usa (ticker direto)
Preços ajustadosAdj Close (opaco)adjusted_close por split, grupamento e dividendo
Fundamentos da CVMVazios ou inconsistentes27 indicadores calculados
Ativos delistados / driftSérie trunca em silêncioHistórico completo desde 1986
Dividendos com DYParcialEndpoint dedicado, DY TTM
FIIs (P/VP e DY)DescobertoEndpoint dedicado
Contrato / estabilidadeSem contrato, quebra sem avisoAPI REST versionada
Integração com IA (MCP)NãoServidor MCP oficial

Vale a nota honesta: o yfinance é gratuito, global e excelente para prototipar com ativos de qualquer mercado. Para um estudante testando uma estratégia ou um notebook que puxa índices internacionais, ele resolve com custo zero. A migração vale a pena quando o foco é a B3 a sério, em produção, com fundamentos e com a exigência de que o número esteja certo. Para escolher com calma entre os provedores brasileiros, vale ler a comparação honesta entre as principais APIs da B3 e o guia sobre qual API de dados da B3 escolher.

Migrando um backtest completo (exemplo end-to-end)

Para fechar, um exemplo real: um backtest simples de buy-and-hold com aporte único, calculando retorno total e drawdown máximo de PETR4 usando a série ajustada. É o tipo de rotina que costuma nascer em cima de yf.download e sofre com séries vazias ou cortadas. Aqui, os dados chegam ajustados e contínuos.

import httpx
import pandas as pd

API_KEY = "sua_chave_aqui"
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": API_KEY}

def carregar(ticker, start):
    r = httpx.get(
        f"{BASE}/stocks/{ticker}/history",
        headers=HEADERS,
        params={"start": start, "limit": 5000},
    )
    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("PETR4", "2020-01-01")
serie = df["adjusted_close"]

retorno_total = serie.iloc[-1] / serie.iloc[0] - 1
pico = serie.cummax()
drawdown_max = ((serie - pico) / pico).min()

print(f"Retorno total: {retorno_total:.1%}")
print(f"Drawdown máximo: {drawdown_max:.1%}")
# Retorno total: 118.4%
# Drawdown máximo: -34.7%

A mesma função carregar serve para qualquer papel, inclusive tickers que já mudaram de código, porque a série vem completa. Trocar a fonte de preço por uma API oficial elimina a classe inteira de bugs em que o backtest roda sem erro mas sobre dados vazios ou truncados. O resto do pipeline (pandas, numpy, matplotlib) continua igual.

Perguntas frequentes

Por que yf.download retorna vazio para ações da B3?

O yfinance faz scraping não oficial do Yahoo Finance, que expõe ações da B3 sob o sufixo .SA (PETR4.SA). Quando o Yahoo muda um endpoint, aplica rate limit, não tem cobertura para aquele papel ou quando o ticker mudou por evento corporativo, a resposta vem como DataFrame vazio ou com NaN, geralmente com a mensagem "No data found, symbol may be delisted". Não há contrato estável: o mesmo código pode funcionar hoje e falhar amanhã sem aviso.

Existe alternativa gratuita ao yfinance para o Brasil?

Sim. A bolsai oferece um plano gratuito com 200 requisições por dia, sem cartão de crédito, com dados oficiais da B3 e da CVM. Além de preços ajustados desde 1986, entrega 27 indicadores fundamentalistas calculados, dividendos com DY, FIIs e indicadores macro. O plano Pro custa R$49/mês e libera 10.000 requisições/dia. Diferente do yfinance, é uma API REST com contrato versionado e autenticação por header X-API-Key.

Como obter preços ajustados por dividendos da B3?

No endpoint /stocks/{ticker}/history da bolsai, cada registro traz os campos adjusted_open, adjusted_high, adjusted_low e adjusted_close, já ajustados por splits, grupamentos e dividendos. Para backtest e cálculo de retorno total, use adjusted_close no lugar do Adj Close do yfinance. Os campos open/high/low/close sem prefixo trazem o preço nominal do pregão.

O yfinance funciona com FIIs?

Parcialmente. Fundos imobiliários aparecem no Yahoo com o sufixo .SA (HGLG11.SA), mas a cobertura de preço é irregular e não há P/VP nem dividend yield específicos do veículo. A bolsai tem um endpoint dedicado /fiis/{ticker} (ferramenta get_fii_details) com P/VP e DY, além do histórico de proventos, que é o dado central para quem analisa FIIs.

Leia também

Referências

Este conteúdo é educativo e não constitui recomendação de investimento. Os números dos exemplos são ilustrativos e refletem dados de referência de julho de 2026, sujeitos a atualização. yfinance e Yahoo Finance são marcas de seus respectivos detentores; a bolsai não tem vínculo com eles. Consulte a documentação oficial antes de decisões de arquitetura.