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.
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())
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 gratuitaCotaçã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 / Close | open / high / low / close | Preço nominal do pregão |
| Adj Close | adjusted_close | Retorno total, backtest |
| Volume | volume | Volume negociado |
| (não separado) | adjusted_open / high / low | OHLC ajustado completo |
| (index) | trade_date | Data 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 dados | Yahoo Finance (não oficial) | B3 e CVM (oficial) |
| Cobertura da B3 | Secundária, irregular | Dedicada |
Sufixo .SA | Obrigatório, falha comum | Não usa (ticker direto) |
| Preços ajustados | Adj Close (opaco) | adjusted_close por split, grupamento e dividendo |
| Fundamentos da CVM | Vazios ou inconsistentes | 27 indicadores calculados |
| Ativos delistados / drift | Série trunca em silêncio | Histórico completo desde 1986 |
| Dividendos com DY | Parcial | Endpoint dedicado, DY TTM |
| FIIs (P/VP e DY) | Descoberto | Endpoint dedicado |
| Contrato / estabilidade | Sem contrato, quebra sem aviso | API REST versionada |
| Integração com IA (MCP) | Não | Servidor 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
Histórico de preços ajustados da B3 com Python
Como consultar cotação de ações em Python
bolsai vs brapi: comparação honesta das APIs da B3
Referências
- github.com/ranaroussi/yfinance: repositório oficial da biblioteca yfinance e issues sobre dados vazios e símbolos delistados.
- finance.yahoo.com: fonte que o yfinance consome, onde ações da B3 aparecem sob o sufixo
.SA. - b3.com.br: fonte primária de cotações, COTAHIST e eventos corporativos (splits, grupamentos, mudança de ticker).
- Documentação da bolsai: endpoints, parâmetros, campos ajustados e exemplos em Python.
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.