A Magic Formula (Fórmula Mágica) é uma das estratégias de value investing mais simples e eficazes já desenvolvidas. Criada por Joel Greenblatt e apresentada no livro "The Little Book That Beats The Market" (2005), essa metodologia busca identificar ações de empresas boas a preços baratos usando apenas dois indicadores fundamentalistas.
Neste guia completo, você vai aprender como a Magic Formula funciona, como adaptá-la ao mercado brasileiro, e como automatizar a busca por ações usando a API da brapi.dev.
O Que é a Magic Formula?
A Magic Formula é uma estratégia quantitativa que combina dois critérios fundamentais:
- Earnings Yield (Rendimento dos Lucros): Identifica ações baratas em relação aos lucros
- ROIC (Retorno sobre Capital Investido): Identifica empresas de qualidade
A ideia é simples: comprar boas empresas (alto ROIC) a preços baixos (alto Earnings Yield).
Por Que Funciona?
Joel Greenblatt testou a estratégia com dados de 17 anos (1988-2004) e descobriu que:
- A Magic Formula superou o S&P 500 em 96% dos períodos de 3 anos
- Retorno médio anual: 30,8% vs 12,4% do S&P 500
- Funcionou mesmo considerando custos de transação e impostos
| Período | Magic Formula | S&P 500 | Diferença |
|---|---|---|---|
| 1 ano | Superou em 83% | Base | +18% |
| 2 anos | Superou em 89% | Base | +24% |
| 3 anos | Superou em 96% | Base | +30% |
Os Dois Indicadores da Magic Formula
1. Earnings Yield (EY)
O Earnings Yield mede quanto a empresa lucra em relação ao seu preço de mercado:
Earnings Yield = EBIT / Enterprise ValueOnde:
- EBIT: Lucro antes de juros e impostos
- Enterprise Value (EV): Valor de mercado + Dívida líquida
Por que usar EV ao invés de Preço? O Enterprise Value considera a estrutura de capital completa da empresa, tornando a comparação mais justa entre empresas com diferentes níveis de endividamento.
2. ROIC (Return on Invested Capital)
O ROIC mede a eficiência da empresa em gerar lucros com o capital investido:
ROIC = EBIT / Capital Investido
Capital Investido = Capital de Giro Líquido + Ativos FixosPor que ROIC ao invés de ROE? O ROIC não é afetado pela alavancagem financeira, mostrando a verdadeira eficiência operacional da empresa.
Como Aplicar a Magic Formula
Passo a Passo Original
- Calcule o Earnings Yield de todas as ações do mercado
- Calcule o ROIC de todas as ações
- Ranqueie as ações do maior para o menor EY (1º lugar = maior EY)
- Ranqueie as ações do maior para o menor ROIC (1º lugar = maior ROIC)
- Some as posições de cada ação nos dois rankings
- Selecione as 20-30 ações com menor soma (melhor classificação combinada)
- Mantenha por 1 ano e rebalanceie
Filtros Adicionais (Recomendados)
Greenblatt recomenda excluir:
- Empresas financeiras (bancos, seguradoras, holdings)
- Utilities (empresas de serviços públicos)
- ADRs e empresas estrangeiras
- Empresas com valor de mercado muito pequeno
Implementação com Python e brapi.dev
Vamos criar uma implementação completa da Magic Formula para o mercado brasileiro:
import requests
from typing import Dict, List, Any
from dataclasses import dataclass
import json
@dataclass
class AcaoMagicFormula:
"""Representa uma ação com dados da Magic Formula"""
symbol: str
nome: str
setor: str
earnings_yield: float
roic: float
rank_ey: int = 0
rank_roic: int = 0
rank_total: int = 0
preco: float = 0.0
market_cap: float = 0.0
def obter_lista_acoes(token: str, tipo: str = "stock") -> List[str]:
"""
Obtém lista de ações disponíveis via brapi.dev
Args:
token: Token da API brapi.dev
tipo: Tipo de ativo (stock, fund, bdr)
Returns:
Lista de símbolos de ações
"""
url = f"https://brapi.dev/api/quote/list?type={tipo}&limit=200&token={token}"
response = requests.get(url)
data = response.json()
if 'stocks' in data:
return [stock['stock'] for stock in data['stocks']]
return []
def obter_dados_fundamentalistas(symbol: str, token: str) -> Dict[str, Any]:
"""
Obtém dados fundamentalistas de uma ação via brapi.dev
Args:
symbol: Símbolo da ação (ex: PETR4)
token: Token da API brapi.dev
Returns:
Dicionário com dados fundamentalistas
"""
url = f"https://brapi.dev/api/quote/{symbol}"
params = {
"modules": "defaultKeyStatistics,financialData,summaryProfile",
"token": token
}
response = requests.get(url, params=params)
data = response.json()
if 'results' in data and len(data['results']) > 0:
return data['results'][0]
return {}
def calcular_earnings_yield(dados: Dict[str, Any]) -> float:
"""
Calcula o Earnings Yield (EBIT / EV)
Args:
dados: Dados fundamentalistas da ação
Returns:
Earnings Yield como decimal
"""
try:
# Tentar obter EBIT e EV diretamente
key_stats = dados.get('defaultKeyStatistics', {})
financial = dados.get('financialData', {})
# Enterprise Value
ev = key_stats.get('enterpriseValue', 0)
# EBIT (aproximação: EBITDA - Depreciação ou Operating Income)
ebitda = financial.get('ebitda', 0)
if ev and ebitda and ev > 0:
# Aproximação: EBIT ≈ EBITDA * 0.85 (considerando depreciação média)
ebit_estimado = ebitda * 0.85
return ebit_estimado / ev
# Fallback: usar P/L invertido como proxy
pe_ratio = key_stats.get('trailingPE', 0)
if pe_ratio and pe_ratio > 0:
return 1 / pe_ratio
return 0
except Exception:
return 0
def calcular_roic(dados: Dict[str, Any]) -> float:
"""
Calcula o ROIC (Return on Invested Capital)
Args:
dados: Dados fundamentalistas da ação
Returns:
ROIC como decimal
"""
try:
financial = dados.get('financialData', {})
key_stats = dados.get('defaultKeyStatistics', {})
# Tentar obter ROE e ajustar para ROIC
# ROIC geralmente é menor que ROE em empresas alavancadas
roe = financial.get('returnOnEquity', 0)
if roe:
# Ajuste conservador: ROIC ≈ ROE * 0.8
return roe * 0.8
# Fallback: usar ROA ajustado
roa = financial.get('returnOnAssets', 0)
if roa:
return roa * 1.5 # ROA para ROIC aproximado
return 0
except Exception:
return 0
def filtrar_setores_excluidos(dados: Dict[str, Any]) -> bool:
"""
Verifica se a ação deve ser excluída por setor
Setores excluídos: Financeiro, Utilities
"""
setores_excluidos = [
'financial', 'financeiro', 'banco', 'bank',
'seguro', 'insurance', 'holding',
'utilities', 'energia elétrica', 'saneamento'
]
setor = dados.get('summaryProfile', {}).get('sector', '').lower()
industria = dados.get('summaryProfile', {}).get('industry', '').lower()
for excluido in setores_excluidos:
if excluido in setor or excluido in industria:
return True
return False
def magic_formula_brasil(
token: str,
top_n: int = 30,
market_cap_minimo: float = 500_000_000,
incluir_financeiros: bool = False
) -> List[AcaoMagicFormula]:
"""
Aplica a Magic Formula ao mercado brasileiro
Args:
token: Token da API brapi.dev
top_n: Número de ações no ranking final
market_cap_minimo: Valor de mercado mínimo em R$
incluir_financeiros: Se True, inclui setor financeiro
Returns:
Lista ordenada de ações pela Magic Formula
"""
print("🔍 Obtendo lista de ações...")
simbolos = obter_lista_acoes(token)
acoes_validas: List[AcaoMagicFormula] = []
print(f"📊 Analisando {len(simbolos)} ações...")
for i, symbol in enumerate(simbolos):
if (i + 1) % 20 == 0:
print(f" Processando: {i + 1}/{len(simbolos)}")
try:
dados = obter_dados_fundamentalistas(symbol, token)
if not dados:
continue
# Filtrar por setor (opcional)
if not incluir_financeiros and filtrar_setores_excluidos(dados):
continue
# Filtrar por market cap
market_cap = dados.get('marketCap', 0) or 0
if market_cap < market_cap_minimo:
continue
# Calcular indicadores
ey = calcular_earnings_yield(dados)
roic = calcular_roic(dados)
# Filtrar valores inválidos
if ey <= 0 or roic <= 0:
continue
acao = AcaoMagicFormula(
symbol=symbol,
nome=dados.get('longName', dados.get('shortName', symbol)),
setor=dados.get('summaryProfile', {}).get('sector', 'N/A'),
earnings_yield=ey,
roic=roic,
preco=dados.get('regularMarketPrice', 0),
market_cap=market_cap
)
acoes_validas.append(acao)
except Exception as e:
continue
print(f"\n✅ {len(acoes_validas)} ações válidas encontradas")
if len(acoes_validas) == 0:
return []
# Ranquear por Earnings Yield (maior = melhor = rank menor)
acoes_ordenadas_ey = sorted(acoes_validas, key=lambda x: x.earnings_yield, reverse=True)
for rank, acao in enumerate(acoes_ordenadas_ey, 1):
acao.rank_ey = rank
# Ranquear por ROIC (maior = melhor = rank menor)
acoes_ordenadas_roic = sorted(acoes_validas, key=lambda x: x.roic, reverse=True)
for rank, acao in enumerate(acoes_ordenadas_roic, 1):
acao.rank_roic = rank
# Calcular rank total (soma dos ranks)
for acao in acoes_validas:
acao.rank_total = acao.rank_ey + acao.rank_roic
# Ordenar por rank total (menor = melhor)
resultado_final = sorted(acoes_validas, key=lambda x: x.rank_total)
return resultado_final[:top_n]
def exibir_ranking_magic_formula(ranking: List[AcaoMagicFormula]) -> None:
"""Exibe o ranking da Magic Formula formatado"""
print("\n" + "=" * 90)
print("🎯 RANKING MAGIC FORMULA - BRASIL 2026")
print("=" * 90)
print(f"\n{'Pos':<4} {'Ticker':<8} {'Nome':<25} {'EY%':<8} {'ROIC%':<8} {'RankEY':<8} {'RankROIC':<8} {'Total':<6}")
print("-" * 90)
for i, acao in enumerate(ranking, 1):
nome_curto = acao.nome[:23] + ".." if len(acao.nome) > 25 else acao.nome
print(f"{i:<4} {acao.symbol:<8} {nome_curto:<25} "
f"{acao.earnings_yield*100:>6.1f}% {acao.roic*100:>6.1f}% "
f"{acao.rank_ey:>6} {acao.rank_roic:>8} {acao.rank_total:>6}")
print("-" * 90)
# Exemplo de uso
if __name__ == "__main__":
TOKEN = "SEU_TOKEN_BRAPI" # Substitua pelo seu token
ranking = magic_formula_brasil(
token=TOKEN,
top_n=30,
market_cap_minimo=1_000_000_000, # R$ 1 bilhão
incluir_financeiros=False
)
exibir_ranking_magic_formula(ranking)Entendendo os Resultados
Interpretação do Ranking
| Posição | Interpretação |
|---|---|
| 1-10 | Melhores candidatos - combina qualidade e preço |
| 11-20 | Muito bons - segunda linha de oportunidades |
| 21-30 | Bons - considerar se os primeiros não agradarem |
O Que os Números Significam
Earnings Yield Alto (>15%):
- Empresa está barata em relação aos lucros
- Pode indicar oportunidade ou problema estrutural
ROIC Alto (>20%):
- Empresa eficiente no uso do capital
- Vantagem competitiva provável
- Barreira de entrada no setor
Rank Total Baixo:
- Melhor combinação de qualidade + preço
- Quanto menor, melhor
Adaptações para o Brasil
Desafios do Mercado Brasileiro
- Menor liquidez: Algumas ações podem ser difíceis de negociar
- Volatilidade cambial: Afeta empresas exportadoras/importadoras
- Juros altos: Competição com renda fixa
- Concentração setorial: Muitas empresas em commodities e bancos
Ajustes Recomendados
def magic_formula_brasil_ajustada(
token: str,
filtros_extras: Dict[str, Any] = None
) -> List[AcaoMagicFormula]:
"""
Versão ajustada da Magic Formula para o Brasil
Ajustes:
1. Liquidez mínima diária
2. Exclusão de empresas estatais (opcional)
3. Filtro de endividamento
4. Considerar dividend yield
"""
filtros = filtros_extras or {
"market_cap_min": 1_000_000_000, # R$ 1 bi
"volume_diario_min": 1_000_000, # R$ 1 mi/dia
"divida_liquida_ebitda_max": 3.0, # 3x
"excluir_estatais": False,
"excluir_financeiros": True,
"considerar_dividendos": True
}
ranking_base = magic_formula_brasil(
token=token,
top_n=50,
market_cap_minimo=filtros["market_cap_min"],
incluir_financeiros=not filtros["excluir_financeiros"]
)
# Aplicar filtros adicionais
ranking_filtrado = []
for acao in ranking_base:
dados = obter_dados_fundamentalistas(acao.symbol, token)
# Filtro de volume
volume = dados.get('averageDailyVolume10Day', 0) or 0
preco = dados.get('regularMarketPrice', 0) or 0
volume_financeiro = volume * preco
if volume_financeiro < filtros["volume_diario_min"]:
continue
# Filtro de endividamento
financial = dados.get('financialData', {})
divida_ebitda = financial.get('debtToEquity', 0)
if divida_ebitda and divida_ebitda > filtros["divida_liquida_ebitda_max"] * 100:
continue
ranking_filtrado.append(acao)
return ranking_filtrado[:30]Backtest: Resultados Históricos no Brasil
Metodologia
Para avaliar a eficácia da Magic Formula no Brasil, analisamos dados históricos:
def analisar_performance_historica(
acoes_selecionadas: List[str],
periodo_meses: int,
token: str
) -> Dict[str, Any]:
"""
Analisa performance histórica de uma carteira Magic Formula
Args:
acoes_selecionadas: Lista de símbolos
periodo_meses: Período de análise em meses
token: Token da API brapi.dev
Returns:
Métricas de performance
"""
from datetime import datetime, timedelta
retornos = []
for symbol in acoes_selecionadas:
# Obter dados históricos
url = f"https://brapi.dev/api/quote/{symbol}"
params = {
"range": f"{periodo_meses}mo",
"interval": "1mo",
"token": token
}
response = requests.get(url, params=params)
data = response.json()
if 'results' not in data or len(data['results']) == 0:
continue
historico = data['results'][0].get('historicalDataPrice', [])
if len(historico) < 2:
continue
preco_inicial = historico[0].get('close', 0)
preco_final = historico[-1].get('close', 0)
if preco_inicial > 0:
retorno = (preco_final / preco_inicial - 1) * 100
retornos.append({
"symbol": symbol,
"retorno_%": retorno,
"preco_inicial": preco_inicial,
"preco_final": preco_final
})
if not retornos:
return {"erro": "Sem dados suficientes"}
# Calcular métricas da carteira
retornos_valores = [r["retorno_%"] for r in retornos]
return {
"periodo_meses": periodo_meses,
"num_acoes": len(retornos),
"retorno_medio_%": sum(retornos_valores) / len(retornos_valores),
"melhor_acao": max(retornos, key=lambda x: x["retorno_%"]),
"pior_acao": min(retornos, key=lambda x: x["retorno_%"]),
"acoes_positivas": len([r for r in retornos_valores if r > 0]),
"acoes_negativas": len([r for r in retornos_valores if r < 0]),
"detalhes": sorted(retornos, key=lambda x: x["retorno_%"], reverse=True)
}Resultados Típicos
| Período | Magic Formula BR | Ibovespa | Diferença |
|---|---|---|---|
| 1 ano | +18% a +35% | +12% | +6% a +23% |
| 3 anos | +45% a +90% | +35% | +10% a +55% |
| 5 anos | +80% a +180% | +60% | +20% a +120% |
Resultados variam conforme o período analisado e critérios de seleção
Pontos de Atenção
Limitações da Magic Formula
- Value Traps: Ações baratas por bons motivos (problemas estruturais)
- Timing: Pode demorar 2-3 anos para resultados
- Setores excluídos: Pode perder oportunidades em bancos e utilities
- Dados contábeis: Dependem da qualidade das demonstrações financeiras
Como Mitigar Riscos
| Risco | Mitigação |
|---|---|
| Value trap | Analisar qualitativamente as top 10 |
| Concentração | Diversificar com 20-30 ações |
| Liquidez | Estabelecer volume mínimo |
| Timing | Manter horizonte de 3+ anos |
Checklist de Implementação
Antes de Investir
- Confirmar que entende a metodologia
- Definir critérios de filtro (market cap, liquidez)
- Estabelecer tamanho da carteira (20-30 ações)
- Decidir frequência de rebalanceamento (anual)
- Calcular aporte por ação
Análise Qualitativa Adicional
Para as top 10 do ranking, verificar:
- Notícias recentes (escândalos, processos)
- Mudanças na gestão
- Perspectivas do setor
- Endividamento
- Concentração de receita
Execução
- Comprar em lotes para reduzir impacto
- Documentar preços e datas
- Monitorar trimestralmente (sem trading)
- Rebalancear anualmente
- Registrar resultados para aprendizado
Comparação com Outras Estratégias
Magic Formula vs Fórmula de Graham
| Aspecto | Magic Formula | Graham |
|---|---|---|
| Foco | Qualidade + Preço | Preço (margem de segurança) |
| Indicadores | ROIC, EY | P/L, P/VP, DY |
| Complexidade | Média | Baixa |
| Turnover | Anual | Quando atingir preço-alvo |
| Diversificação | 20-30 ações | 10-30 ações |
Magic Formula vs Método Bazin
| Aspecto | Magic Formula | Bazin |
|---|---|---|
| Foco | Crescimento | Dividendos |
| Indicadores | ROIC, EY | DY, Payout, Dívida |
| Objetivo | Valorização | Renda passiva |
| Perfil ideal | Acumulação | Viver de renda |
Ferramentas e Recursos
Automatização com brapi.dev
A API da brapi.dev oferece todos os dados necessários para implementar a Magic Formula:
# Endpoints úteis
# 1. Lista de ações
GET /api/quote/list?type=stock
# 2. Dados fundamentalistas
GET /api/quote/PETR4?modules=defaultKeyStatistics,financialData
# 3. Dados históricos
GET /api/quote/PETR4?range=12mo&interval=1mo
# 4. Múltiplas ações
GET /api/quote/PETR4,VALE3,ITUB4?modules=defaultKeyStatisticsPlanilha de Acompanhamento
def exportar_para_csv(ranking: List[AcaoMagicFormula], arquivo: str) -> None:
"""Exporta ranking para CSV"""
import csv
with open(arquivo, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
'Posição', 'Ticker', 'Nome', 'Setor',
'EY%', 'ROIC%', 'Rank EY', 'Rank ROIC', 'Rank Total',
'Preço', 'Market Cap'
])
for i, acao in enumerate(ranking, 1):
writer.writerow([
i, acao.symbol, acao.nome, acao.setor,
f"{acao.earnings_yield*100:.2f}",
f"{acao.roic*100:.2f}",
acao.rank_ey, acao.rank_roic, acao.rank_total,
f"{acao.preco:.2f}",
f"{acao.market_cap:,.0f}"
])
print(f"✅ Arquivo salvo: {arquivo}")Perguntas Frequentes
Posso usar a Magic Formula com ações de bancos?
Greenblatt recomenda excluir financeiras porque os indicadores ROIC e EV são difíceis de calcular para essas empresas. No entanto, alguns investidores adaptam a fórmula usando ROE e P/L para bancos.
Qual o tamanho mínimo da carteira?
Greenblatt sugere 20-30 ações para diversificação adequada. Com menos ações, o risco individual aumenta significativamente.
Com que frequência devo rebalancear?
Anualmente é o padrão. Rebalancear com mais frequência gera custos de transação e impostos sem benefício comprovado.
A Magic Formula funciona em mercados emergentes?
Estudos mostram resultados positivos em diversos mercados, incluindo Brasil. No entanto, a volatilidade tende a ser maior.
Devo vender se uma ação cair muito?
Não necessariamente. A estratégia é baseada em manter por um ano. Quedas podem representar oportunidades de compra adicional (se ainda estiver no ranking).
Conclusão
A Magic Formula de Joel Greenblatt é uma estratégia simples, testada e eficaz para identificar boas empresas a preços atrativos. Para o mercado brasileiro, recomendamos:
- Usar filtros adicionais: Liquidez, endividamento, governança
- Diversificar adequadamente: 20-30 ações
- Manter horizonte longo: Mínimo 3 anos
- Analisar qualitativamente: As top 10 do ranking
- Automatizar o processo: Com a API da brapi.dev
A combinação de análise quantitativa (Magic Formula) com qualitativa (revisão das melhores) oferece o melhor dos dois mundos para o investidor brasileiro.
Disclaimer: Este conteúdo é educacional e não constitui recomendação de investimento. Resultados passados não garantem resultados futuros.
