Quem investe em FIIs sabe: o "salário extra" mensal não cai todo no mesmo dia. HGLG11 paga uma data, MXRF11 outra, KNCR11 outra. Sem organização, você só descobre o pagamento quando aparece no extrato. Neste tutorial você vai construir um calendário de dividendos de FIIs em Python, com projeções e notificações.
O Problema
Imagine uma carteira modesta com 8 FIIs. Cada um tem:
- Sua data com (ex-dividend) própria, geralmente perto do início do mês
- Sua data de pagamento (4 a 14 dias depois), variando por administrador
- Seu valor por cota, que oscila levemente entre meses
Acompanhar isso "no olho" via app da corretora é frustrante:
- Você não tem visão consolidada do mês
- Não sabe quando o próximo dinheiro vai entrar
- Não consegue planejar reinvestimento ou pagar contas usando a renda
Vamos resolver isso construindo um pipeline que:
- Puxa o histórico via
/api/v2/fii/dividends - Identifica a cadência de pagamento de cada FII
- Projeta os próximos 6 meses com base no padrão histórico
- Exporta para um arquivo
.ics(Google Calendar, Outlook, Apple Calendar) - Opcionalmente envia notificações via Telegram
Cuidado importante: os relatórios CVM mensais (que populam
/api/v2/fii/dividends) não incluem a data de pagamento real — apenas
a data de competência. A brapi consolida o que está disponível no relatório
e o paymentDate reflete a referência da competência. Para a data exata de
crédito, sempre confirme no aviso de rendimento do FII publicado no
fato relevante. Esse aviso geralmente sai 1 a 5 dias úteis antes do
pagamento.
O Que a API Retorna
curl "https://brapi.dev/api/v2/fii/dividends?symbols=MXRF11&startDate=2024-01-01&endDate=2025-12-31"Resposta abreviada:
{
"dividends": [
{
"symbol": "MXRF11",
"approvedOn": "2025-04-14T00:00:00.000Z",
"label": "RENDIMENTO", // RENDIMENTO ou AMORTIZAÇÃO
"lastDatePrior": "2025-04-14T00:00:00.000Z", // Data com (ex-dividend)
"paymentDate": "2025-04-25T00:00:00.000Z", // Data de pagamento (referência)
"rate": 0.10, // R$ por cota
"relatedTo": "Março/2025",
"isinCode": "BRMXRFCTF007",
"remarks": ""
},
/* ... */
]
}A grande diferença em relação aos dividendos de ações: FIIs pagam mensalmente, com cadência muito previsível para a maioria dos fundos consolidados.
O Que Vamos Construir
fii-calendar/
├── brapi_client.py # Cliente HTTP
├── projector.py # Lógica de projeção
├── exporter.py # Geração de .ics
├── notifier.py # Telegram (opcional)
└── run.py # Pipeline principalTempo: ~30 minutos.
Pré-requisitos
mkdir fii-calendar && cd fii-calendar
python -m venv .venv && source .venv/bin/activate
pip install requests pandas ics python-dateutil
export BRAPI_TOKEN="seu_token_pro"Passo 1: Cliente API
import os
import requests
from datetime import date, timedelta
BASE = "https://brapi.dev/api/v2/fii"
TOKEN = os.getenv("BRAPI_TOKEN", "")
HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
def fetch_dividends(
symbols: list[str],
months_back: int = 24,
) -> list[dict]:
"""Busca histórico de dividendos para uma carteira de FIIs."""
today = date.today()
start = (today - timedelta(days=months_back * 31)).isoformat()
end = today.isoformat()
params = {
"symbols": ",".join(symbols),
"startDate": start,
"endDate": end,
"sortBy": "paymentDate",
"sortOrder": "asc",
}
resp = requests.get(f"{BASE}/dividends", params=params, headers=HEADERS, timeout=30)
resp.raise_for_status()
return resp.json().get("dividends", [])A API aceita até 20 símbolos por request. Se sua carteira for maior, basta dividir em lotes.
Passo 2: Análise de Cadência
Cada FII tem padrões próprios. Vamos extrair:
- Dia típico do mês em que o pagamento ocorre
- Valor médio dos últimos N meses
- Coeficiente de variação do valor (estabilidade)
import pandas as pd
from dataclasses import dataclass
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
@dataclass
class FiiCadence:
symbol: str
typical_day_of_month: int
avg_rate: float # R$ por cota
median_rate: float
coef_variation: float # Estabilidade do valor (menor = mais estável)
last_payment: date
last_rate: float
def analyze_cadence(dividends: list[dict]) -> dict[str, FiiCadence]:
"""Calcula a cadência de pagamento de cada FII com base no histórico."""
df = pd.DataFrame(dividends)
if df.empty:
return {}
# Apenas RENDIMENTO (não amortização)
df = df[df["label"].str.upper() == "RENDIMENTO"].copy()
df["paymentDate"] = pd.to_datetime(df["paymentDate"])
df["day_of_month"] = df["paymentDate"].dt.day
df["rate"] = df["rate"].astype(float)
cadences: dict[str, FiiCadence] = {}
for symbol, group in df.groupby("symbol"):
if len(group) < 3:
# Histórico insuficiente para projetar
continue
last_12 = group.sort_values("paymentDate").tail(12)
cadences[symbol] = FiiCadence(
symbol=symbol,
typical_day_of_month=int(last_12["day_of_month"].mode().iloc[0]),
avg_rate=float(last_12["rate"].mean()),
median_rate=float(last_12["rate"].median()),
coef_variation=float(last_12["rate"].std() / last_12["rate"].mean()) if last_12["rate"].mean() > 0 else 0,
last_payment=last_12["paymentDate"].max().date(),
last_rate=float(last_12.sort_values("paymentDate")["rate"].iloc[-1]),
)
return cadences
@dataclass
class ProjectedPayment:
symbol: str
expected_date: date
expected_rate: float
confidence: str # alta, média, baixa
def project_next_payments(
cadences: dict[str, FiiCadence],
months_ahead: int = 6,
) -> list[ProjectedPayment]:
"""Projeta os próximos N meses com base na cadência."""
today = date.today()
projections: list[ProjectedPayment] = []
for symbol, cad in cadences.items():
confidence = (
"alta" if cad.coef_variation < 0.10
else "média" if cad.coef_variation < 0.25
else "baixa"
)
for i in range(1, months_ahead + 1):
target_month = today + relativedelta(months=+i)
day = min(cad.typical_day_of_month, _last_day_of_month(target_month).day)
expected_date = target_month.replace(day=day)
projections.append(
ProjectedPayment(
symbol=symbol,
expected_date=expected_date,
# Usa mediana (mais robusta a outliers)
expected_rate=cad.median_rate,
confidence=confidence,
)
)
return sorted(projections, key=lambda p: p.expected_date)
def _last_day_of_month(d: date) -> date:
next_month = d.replace(day=28) + timedelta(days=4)
return next_month - timedelta(days=next_month.day)A lógica de confiança é simples mas útil:
- Alta: variação < 10% — FIIs de papel high-grade ou tijolo estável (HGLG11, MXRF11)
- Média: variação 10–25% — FIIs com renda recorrente mas algum overshoot
- Baixa: variação > 25% — fofs ou high yield com grande variabilidade
Passo 3: Estimando Renda Mensal Projetada
Para uma carteira com quantidade de cotas conhecida:
@dataclass
class Holding:
symbol: str
shares: int
def expected_monthly_income(
holdings: list[Holding],
cadences: dict[str, FiiCadence],
months_ahead: int = 6,
) -> pd.DataFrame:
"""Projeta a receita mensal esperada da carteira."""
projections = project_next_payments(cadences, months_ahead=months_ahead)
# Mapa symbol -> cotas
shares_map = {h.symbol: h.shares for h in holdings}
rows = []
for p in projections:
shares = shares_map.get(p.symbol, 0)
rows.append({
"month": p.expected_date.strftime("%Y-%m"),
"expected_date": p.expected_date.isoformat(),
"symbol": p.symbol,
"shares": shares,
"rate": p.expected_rate,
"income": shares * p.expected_rate,
"confidence": p.confidence,
})
df = pd.DataFrame(rows)
return dfUso:
holdings = [
Holding(symbol="HGLG11", shares=50),
Holding(symbol="MXRF11", shares=1000),
Holding(symbol="KNCR11", shares=80),
Holding(symbol="VISC11", shares=120),
Holding(symbol="BTLG11", shares=200),
]
dividends = fetch_dividends([h.symbol for h in holdings])
cadences = analyze_cadence(dividends)
df = expected_monthly_income(holdings, cadences, months_ahead=6)
# Receita por mês
print(df.groupby("month")["income"].sum().round(2))Saída exemplo:
month
2026-06 1245.30
2026-07 1245.30
2026-08 1245.30
2026-09 1245.30
2026-10 1245.30
2026-11 1245.30Você ainda pode somar com expectativas de pagamentos extraordinários (datas relacionadas a vendas de imóveis, amortizações), mas como projeção base, isso já dá um número confiável para planejamento.
Passo 4: Exportando para Calendário (.ics)
Esta é a parte que muda a vida: importar tudo no Google Calendar / Outlook / Apple Calendar e ver os pagamentos no mesmo lugar onde você vê reuniões.
from ics import Calendar, Event
from datetime import datetime
def build_ics(
projections: list,
holdings_map: dict[str, int],
) -> str:
"""Gera um arquivo .ics com todos os pagamentos projetados."""
cal = Calendar()
for p in projections:
shares = holdings_map.get(p.symbol, 0)
income = shares * p.expected_rate
event = Event()
event.name = f"💰 {p.symbol} — R$ {income:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
event.begin = datetime.combine(p.expected_date, datetime.min.time())
event.make_all_day()
event.description = (
f"Projeção: R$ {p.expected_rate:.4f} por cota × {shares} cotas\n"
f"Confiança: {p.confidence}\n\n"
f"Atenção: data baseada no padrão histórico. Confirme no fato\n"
f"relevante do FII publicado pelo administrador."
)
cal.events.add(event)
return cal.serialize()
def save_ics(content: str, output_path: str = "fii_calendar.ics") -> None:
with open(output_path, "w", encoding="utf-8") as f:
f.write(content)
print(f"Calendário salvo em {output_path}")Uso final:
projections = project_next_payments(cadences, months_ahead=6)
holdings_map = {h.symbol: h.shares for h in holdings}
content = build_ics(projections, holdings_map)
save_ics(content, "minha_carteira_fii.ics")Como importar:
- Google Calendar: Settings → Import → suba o arquivo
.ics - Outlook: File → Open & Export → Import/Export → Import an iCalendar
- Apple Calendar: File → Import → selecione o arquivo
Resultado: cada FII vira um evento de dia inteiro com o valor projetado no título — perfeito para visualização móvel.
Passo 5: Notificações via Telegram (Opcional)
Se você quer avisos por mensagem N dias antes da data com:
import os
import requests
from datetime import date, timedelta
TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "")
def send_telegram(message: str) -> None:
if not (TELEGRAM_TOKEN and TELEGRAM_CHAT_ID):
print("Telegram não configurado, pulando")
return
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
requests.post(
url,
json={
"chat_id": TELEGRAM_CHAT_ID,
"text": message,
"parse_mode": "Markdown",
},
timeout=15,
)
def upcoming_alerts(projections: list, days_ahead: int = 3) -> None:
"""Envia alerta para pagamentos nos próximos N dias."""
today = date.today()
target = today + timedelta(days=days_ahead)
upcoming = [p for p in projections if today <= p.expected_date <= target]
if not upcoming:
return
lines = ["📅 *Pagamentos de FIIs nos próximos dias:*", ""]
for p in upcoming:
lines.append(
f"• `{p.expected_date.isoformat()}` — *{p.symbol}* — "
f"R$ {p.expected_rate:.4f}/cota _(confiança {p.confidence})_"
)
send_telegram("\n".join(lines))Para criar um bot Telegram e pegar o chat_id, veja o nosso tutorial dedicado: Bot de Telegram para Cotações.
Passo 6: Pipeline Completo
from brapi_client import fetch_dividends
from projector import (
Holding,
analyze_cadence,
project_next_payments,
expected_monthly_income,
)
from exporter import build_ics, save_ics
from notifier import upcoming_alerts
def main() -> None:
holdings = [
Holding(symbol="HGLG11", shares=50),
Holding(symbol="MXRF11", shares=1000),
Holding(symbol="KNCR11", shares=80),
Holding(symbol="VISC11", shares=120),
Holding(symbol="BTLG11", shares=200),
]
symbols = [h.symbol for h in holdings]
print(f"Baixando histórico de {len(symbols)} FIIs...")
dividends = fetch_dividends(symbols, months_back=24)
print("Analisando cadência...")
cadences = analyze_cadence(dividends)
for sym, cad in cadences.items():
print(
f" {sym}: dia ~{cad.typical_day_of_month}, "
f"mediana R$ {cad.median_rate:.4f}, "
f"variação {cad.coef_variation*100:.1f}%"
)
print("\nProjetando próximos 6 meses...")
projections = project_next_payments(cadences, months_ahead=6)
holdings_map = {h.symbol: h.shares for h in holdings}
df = expected_monthly_income(holdings, cadences, months_ahead=6)
print("\nReceita esperada por mês:")
print(df.groupby("month")["income"].sum().round(2))
print("\nGerando calendário .ics...")
content = build_ics(projections, holdings_map)
save_ics(content, "fii_calendar.ics")
print("\nVerificando alertas dos próximos 3 dias...")
upcoming_alerts(projections, days_ahead=3)
if __name__ == "__main__":
main()Rode com:
python run.pyVariações da Estratégia
A) Comparando Renda Projetada vs Realizada
Acumule meses de projeção e compare com o realizado para identificar drift no padrão de pagamentos:
def projection_accuracy(
projected: pd.DataFrame, realized: pd.DataFrame
) -> pd.DataFrame:
merged = projected.merge(
realized[["symbol", "month", "actual_income"]],
on=["symbol", "month"],
how="left",
)
merged["error_pct"] = (
(merged["actual_income"] - merged["income"]) / merged["income"] * 100
)
return mergedErros consistentemente positivos sugerem que os FIIs estão pagando acima do padrão (bom sinal). Erros negativos persistentes podem indicar mudança no perfil do fundo.
B) Heatmap Mensal de Pagamentos
Visualize a distribuição dos pagamentos ao longo do mês:
import matplotlib.pyplot as plt
df = pd.DataFrame(dividends)
df["paymentDate"] = pd.to_datetime(df["paymentDate"])
df["day"] = df["paymentDate"].dt.day
# Histograma do dia do mês com mais pagamentos
df["day"].hist(bins=31)
plt.xlabel("Dia do Mês")
plt.ylabel("Número de Pagamentos")
plt.title("Concentração de Pagamentos de FIIs ao Longo do Mês")
plt.savefig("fii_payment_heatmap.png", dpi=120)A maior parte dos pagamentos se concentra entre os dias 13 e 25 do mês. Saber disso ajuda a planejar reinvestimentos e contas mensais.
C) Dividend Yield Realizado por Mês
def dy_realized(
holdings: list[Holding],
dividends: list[dict],
cost_basis: dict[str, float], # PM por cota investido
) -> pd.DataFrame:
"""DY mensal sobre o preço médio que você pagou."""
df = pd.DataFrame(dividends)
df["paymentDate"] = pd.to_datetime(df["paymentDate"])
df["month"] = df["paymentDate"].dt.to_period("M")
rows = []
for h in holdings:
pm = cost_basis.get(h.symbol, 0)
if pm == 0:
continue
sub = df[df["symbol"] == h.symbol]
for month, group in sub.groupby("month"):
rate = float(group["rate"].sum())
dy_mes = rate / pm
rows.append({
"symbol": h.symbol,
"month": str(month),
"dy_pct": dy_mes * 100,
"yoc_anual_pct": dy_mes * 12 * 100,
})
return pd.DataFrame(rows)YOC (Yield on Cost) é o DY sobre o preço que você pagou, não sobre o preço atual — métrica mais útil para investidores de longo prazo.
Cuidados Importantes
paymentDate é referência, não data exata
Os relatórios CVM monthly não trazem a data exata de crédito. O paymentDate
da brapi reflete a referência de competência. A data real costuma ser de 4 a
14 dias após a lastDatePrior, dependendo do administrador. Para a data
exata, sempre cheque o aviso de rendimento publicado pelo FII no fato
relevante.
Projeção é baseada em histórico
Mudanças no portfólio do FII (vendas de imóveis, captação, mudança de gestão) podem alterar drasticamente a distribuição. Use o calendário como guideline, não como certeza.
Amortização ≠ Rendimento
O endpoint retorna tanto RENDIMENTO quanto AMORTIZAÇÃO. Amortização é
devolução de capital — não é renda nova! Esta implementação filtra apenas
RENDIMENTO no analyze_cadence para evitar inflar a projeção.
Indo Além
A partir desse esqueleto, você pode evoluir para:
- Dashboard web (Streamlit ou Next.js) com a projeção interativa
- Reinvestimento automático que casa o pagamento com a próxima compra
- Alertas de dividendo extraordinário (rendimento muito acima da mediana)
- Comparação entre FIIs com a metodologia de análise de relatórios CVM
Conclusão
FIIs são, no Brasil, a forma mais consistente de gerar renda passiva mensal. Mas a falta de visibilidade sobre quando e quanto é o que faz muita gente desistir antes de colher o efeito da bola de neve.
Com o endpoint /api/v2/fii/dividends da brapi e ~250 linhas de Python, você passa a ter:
- Calendário projetado dos próximos 6 meses
- Renda mensal esperada por carteira
- Notificações automáticas
- Histórico para auditoria de YOC e DY realizado
Próximos passos: se você quer descobrir quais FIIs adicionar à carteira, comece pelo FII Screener com Python. Para análise de fundamentos, veja o tutorial de Relatórios CVM.
Disclaimer: este artigo é educacional. Projeções de dividendos não são garantia de pagamento futuro. FIIs são renda variável.
