All notable changes to this project are documented here. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
- Public MCP
cvm_fundquotes/structure parity (CVM only).dataset=dailyacceptsstart/endormonthsup to 12, stitches a single-class RCVM 175 continuation onto legacy 555 INF_DIARIO when the files share sibling CNPJs, and returnsserved[].nicename/ class / subclass for the series actually returned. Multiple subclasses stay grouped (pick_required) — the tool does not pick a FIDC série.dataset=catalogqalso matches classe and subclasse names. Worker tool description maps Mais Retorno data tools ontocvm_funddatasets. Calc/analytics tools stay out. Rate limits unchanged. - Public MCP
cvm_fund(Worker) + RCVM 175 cadastro. The public Worker at/mcpnow resolves an open-ended fund by CNPJ (or name) against official CVMregistro_fundo_classe.zipand returns INF_DIARIO cota/PL/cotistas for a month.cad_fi.csvis not used for that lookup — it omits funds already adapted to Resolução 175. CDA carteira stays off the public Worker (separate delayed monthly feed). FastAPI/CLI gainGET /cvm/funds/cadastro,findata cvm cadastro,findata cvm daily, and digit-normalized INF_DIARIO CNPJ matching. Rate limits on/mcpare unchanged. The Worker scans the deflated CSV in stream so INF_DIARIO does not materialize ~48 MB in RAM.
- Resolver docs: Mais Retorno Free limits.
docs/RESOLVER.mdnow states that the optional cascade step uses the operator's own account and documents the public Free tier (500 credits/month shared by REST+MCP, variable per-call cost, 1-year history, 15 req/s, HTTP 429 until renewal or upgrade), distinguishing REST API key vs MCP OAuth, with dated links to maisretorno.com/mcp and developers.maisretorno.com. Also aligns the cascade wording with runtime behavior: a provider result replaces the current classification (provider ownsconfidence/source); the resolver only prependscascade. - Distribution slug renamed
findata-br→openfindata. The PyPI distribution name is nowopenfindata(pip install openfindata,pip install 'openfindata[b3]'), aligning the package slug with the GitHub repository and echoing the OpenBB-styleopen*convention. The importable package and CLI are unchanged (import findata,findata …). Deploy artifacts follow suit:deploy/openfindata.service(renamed fromdeploy/findata-br.service) and the default container/working-dir names.
- Test hardening and nightly integration checks. Added offline unit/API
coverage for HTTP 429 retries, rate-limit helpers, previously thin sources
(BCB PTAX/Focus, IBGE, CVM companies/financials, Tesouro bonds, B3 quotes via
mocked yfinance), thin REST smoke for those routes, and CLI smoke
(
--help/--version/bcb series/bcb get). Coverage gate (--cov-fail-under=60) runs on the Python 3.12 CI leg. Live@pytest.mark.integrationtests run on a scheduled nightly workflow (andworkflow_dispatch), not on the default PR CI. - Asset-classification resolver —
findata.resolver.resolve_asset(),GET /resolver/resolve, and theresolve_assetMCP tool. Turns any Brazilian asset identifier (ticker/CNPJ/ISIN/name) into a classification mapped to the consolidation taxonomy.macro_classis the asset class (Renda Fixa, Renda Variável, Multimercado, Alternativos, Estruturados); geography is the orthogonalexposureaxis (Brasil/Internacional), so IVVB11 and BDRs are RV + Internacional and a global-mandate FIA is RV + Internacional. Also returnssubclasse,underlying_nature, debenture Lei-12.431 facts with a certainty status (lei_12431_status: confirmed/candidate/not_applicable;isento_status),source,confidence, thecascadewalked, and a structuredsignalstrail (which rule fired on what evidence). Deterministic and offline at its core (a curated ETF/global-fund seed + structural rules), with an injectable external-provider chain (Mais Retorno / CVM-B3 / restricted web search) for low-confidence fallback. Classifies ETFs/funds by underlying (IFRA11 debêntures → RF; IVVB11 ações → RV), defends the COE-never-ETF and "Crédito Estruturado" name-traps, and keeps a heuristic Lei-12.431 isento as acandidatebelow the cascade short-circuit so a provider can confirm it by ISIN. Hardened after cross-host adversarial review and CI review bots: bare-token collisions (IE/LC/LF/MACRO/ACOES/PARTICIPACOES, substringLCI) gated on fund context, public-bond subclasse derived from the bond code (NTN-B → inflation), API length caps,as_ofstamped in America/Sao_Paulo. - ANBIMA Títulos Públicos (TPF) secondary market —
get_tpf(),GET /anbima/tpf, andfindata anbima tpf. Daily reference rates for outstanding federal government bonds (LTN, LFT, NTN-B, NTN-C, NTN-F) from the public filewww.anbima.com.br/informacoes/merc-sec/arqs/ms{ymd}.txt— no key, no auth. Filterable bytitulo(bond type). Hardened after adversarial review: weekend/holiday HTTP 404 returns[](not a 500), accented header tolerated, compact dates validated, download size capped, and failure boundaries logged. Same 404/size hardening applied to the siblingget_debenturespath.
cvm holdingsignored bare-digit CNPJs. The CDA reader compared the CNPJ argument to the stored value as raw strings, but CVM stores CNPJs punctuated (22.187.946/0001-41). A user passing bare digits (22187946000141) — exactly what the--helptext advertises — got "No holdings". Both sides are now normalized to digits before comparing, so punctuated and bare forms return identical results.cvm holdingssilently dropped BLC_2 (cotas de fundos). CDA free-text fund names contain stray double-quotes; the defaultcsvdialect treated them as quote chars and swallowed delimiters/newlines across rows, so whole blocks parsed to garbage and matched nothing — the CLI printed a partial table that looked complete. For a fund-of-funds (FIC) this hid ~99.8% of the portfolio (e.g. Verde FIC's R$ 850.657.058,68 Verde Master position). The reader now passesquoting=csv.QUOTE_NONE, keeping every"literal.
Patch release fixing 5 bugs caught in adversarial review of v0.3.0 by
external LLM (gpt-5.4 xhigh via tmux). Four are real correctness /
availability issues in the registry query path; the fifth is metadata
hygiene in the BCB SGS catalog.
- DoS via FTS5 reserved words in
/registry/lookup.q=OR(alsoAND,NOT,NEAR) raisedsqlite3.OperationalError: fts5: syntax error near "OR"and surfaced as 500 Internal Server Error to any caller._normalize_querynow phrase-quotes reserved tokens so they go to FTS5 as literals, never operators. - FTS5 syntax injection in
/registry/lookup. User-supplied operators (q=PETR4 NOT VALE3) used to alter query semantics and return attacker-controlled results. Now neutralized via the same phrase-quoting pass. - Prefix / fragment search did not work despite the contract in
routers/registry.pyand the docstring inregistry/__init__.pypromising "company-name fragment" / "prefix searches".q=PETROBRreturned 0 hits. Fix: every non-reserved token now gets a*suffix for FTS5 prefix matching.q=PETROBRcorrectly resolves to PETROBRAS. - Labeled identifiers stopped resolving.
q="CNPJ: 33.000.167/0001-01",q="FIP 05886",q="CVM 9512"all returned 0 because the alpha label contaminated the joined-form variant (CNPJ33000167000101doesn't exist as a token). Fix: third query variant — digits-only-joined — rescues these inputs. cdbSGS series flagged as historical-only.code=3946("Taxa média de CDB pré-fixado") was discontinued by BCB in 2012-12; the catalog now documents this in the entry name and a comment so users who pickfindata bcb get cdbaren't surprised by ancient data.
- 9 regression tests covering each of the 4 fixed query-path bugs: reserved-word safety (4 tokens × asserts), prefix matching, labeled CNPJ resolution, FTS injection neutralization. 168 → 176 tests.
_FTS5_RESERVEDconstant lists FTS5 operators we have to escape.- New helper
_escape_token(t)centralizes quote-or-prefix logic;_normalize_querynow produces up to 3 OR'd variants (spaced / joined / digits-only-joined) for maximum input forgiveness.
Cross-source CNPJ resolver — embedded SQLite registry shipped inside the
wheel. Single FTS5 MATCH query handles CNPJ (with or without mask),
B3 ticker, CVM code, SUSEP FIP code, and fuzzy name lookups uniformly,
with BM25 rank discriminating exact-match from fuzzy.
Sprint 5.5 (parallel) added BCB SGS catalog expansion (17 → 71 named series) and ANBIMA IMA historical fetcher.
findata.registry— read-only async store over the embeddedregistry.sqlite(~50k entities: CVM companies, CVM funds, SUSEP). Functions:lookup(query, limit)andget_meta(). Pydantic modelsEntity(with BM25rank) andLookupResult.- REST endpoints:
GET /registry/lookup?q=...&limit=...andGET /registry/meta. Auto-exposed as MCP tools viafastapi-mcp. - CLI:
findata registry lookup <query>andfindata registry meta. Rich table output with rank interpretation footnote. scripts/build_registry.py— offline regenerator that pulls from CVM (companies + funds), SUSEP, and B3 (index portfolios for ticker enrichment). Writes the canonical SQLite + meta KV table includingcontent_sha256for CI no-op detection..github/workflows/rebuild-registry.yml— weekly cron (Mon 06:00 UTC) that rebuilds the registry and opens a PR if content hash differs. Manualworkflow_dispatchtrigger for ad-hoc regeneration.- BCB SGS catalog: 17 → 71 named series — inflation breakdown (livres / monitorados / serviços / bens / núcleos), monetary aggregates (M1–M4), credit (saldo SFN, inadimplência, spread), external sector (balança, conta corrente, IDP, reservas), public sector (DBGG, primário, juros nominais), confidence (ICC FGV, ICEI CNI), and more.
- ANBIMA IMA history:
get_ima_history(family, start, end)fetches historical IMA data points via ANBIMA's "Série Histórica" form (ima-sh-down.aspPOST), validated against 14 years of data. Bounded concurrency, holiday-aware (handles "Não há dados disponíveis" banner), per-date 24h cache. aiosqlite >= 0.20added to core deps (pure Python, ~50KB).
scripts/build_registry.pytoken normalization split: codes (CNPJ, ticker, cod_cvm) collapse punctuation, names preserve word boundaries. Without this fix, masked CNPJ"33.000.167/0001-01"fragmented into five tokens and exact CNPJ search returned 0 hits._normalize_queryproduces(spaced) OR (joined)for inputs with punctuation — masked CNPJ resolves the same as bare digits.
- B3 ticker enrichment misses entities where B3's
nome_ativodiffers from CVM'snome_comercial(e.g.ITAUUNIBANCOvsITAU UNIBANCO). v2 candidate: fuzzy match or B3 endpoint exposingcod_cvmdirectly. - CVM funds dataset (
cad_fi.csv) is dominated byCANCELADAentries (46,566 of 46,810). Likely a legacy endpoint; consumers can filter viaextra.situacaoin the payload. - Same legal entity may appear under multiple
cod_cvmrecords, causing duplicate hits on CNPJ lookups. v2: dedupe on CNPJ at build time.
- Tests: 117 → 168 (+51)
- REST endpoints: 73 → 75 (+2)
- Wheel size: 4.2 MB compressed (28 MB uncompressed; sqlite compresses heavily inside the zip)
- Registry build time: ~5 seconds against live BCB/CVM/SUSEP/B3
Massive expansion of the public-data catalog across four sprints, each
adversarially reviewed by an external LLM (gpt-5.4 xhigh via tmux):
14 real bugs caught and fixed before release. Test count grew from 57
to 117; REST routes from 35 to 73; fonts modeled from 6 to 10.
- CVM IPE — fatos relevantes / comunicados (event stream)
- CVM FCA — formulário cadastral (B3-ticker → CNPJ resolver)
- B3 COTAHIST — official daily-quotes time series since 1986
- B3 indices — composição teórica (IBOV, IBrX, SMLL, IDIV, IFIX + 14 sectoriais)
- CVM FII — Fundos Imobiliários (geral + complemento)
- CVM FIDC — Fundos de Direitos Creditórios (TAB I/IV/VII)
- CVM FIP — Fundos de Investimento em Participações
- Tesouro SICONFI — RREO/RGF/entes (5,598 entidades reportando)
- Receita Federal — arrecadação por UF (45 tributos × meses since 2000)
- ANEEL leilões — geração + transmissão (auction prices since 1999)
- SUSEP empresas — 233 supervised insurance/previdência entities
- ICP-Brasil chain trust via
truststoreintegration in shared http client
Two new sources rounding out the public-data catalog with energy and insurance:
get_aneel_leiloes_geracao(year=, fonte=, uf=)— every winning generation-auction bid since 2005 (A-3 / A-5 / A-6 / LFA / LEN, plus energia nova, energia existente, etc.). 26 columns including potência instalada (MW), garantia física, preço-teto / preço-leilão (R$/MWh), deságio, investimento previsto, duração do contrato, UF e empresa vencedora. Source: ANEEL CKAN (resource UUID stable).get_aneel_leiloes_transmissao(year=, uf=)— every winning transmission-line lot since 1999, with extensão (km), MVA das subestações, RAP de edital × RAP do vencedor, deságio (%), prazo de construção (meses), UF, empresa vencedora.get_susep_empresas / search_susep_empresa— canonical SUSEP roster of every supervised entity (insurance, previdência, capitalização, resseguro). Three columns: CodigoFIP, NomeEntidade, CNPJ. Useful as a code → CNPJ resolver and to filter joint-product feeds. Source:www2.susep.gov.br/menuestatistica/ses/download/LISTAEMPRESAS.csv.
findata.http_clientnow configures httpx with atruststore- backed SSL context when available (added as a core dep,>=0.10), falling back tossl.create_default_context()otherwise. This fixesCERTIFICATE_VERIFY_FAILEDon Brazilian government sites signed under the ICP-Brasil chain (SUSEP being the trigger), which isn't in certifi's bundle but is in macOS Keychain / WSL CA store.
/aneel/leiloes/{geracao,transmissao}/susep/empresas,/susep/empresas/search
findata aneel leiloes [--tipo geracao|transmissao] [-y YYYY] [-f FONTE] [--uf UF]findata susep search <query>
tests/test_aneel_leiloes.py— 6 respx-mocked tests covering Brazilian-decimal parsing, year / fonte / uf filters, RAP + extensão parsing, transmissão filter combinations.tests/test_susep_empresas.py— 3 respx-mocked tests covering malformed-row skip, case-insensitive name search, and short-query refusal.- 114 unit tests pass total (was 105). ruff + ruff-format + mypy --strict clean.
- ANEEL eólicas: 801 winning empreendimentos (Baraúnas XV: 49 MW, R$ 175/MWh; AW São João: 25 MW, R$ 178/MWh).
- ANEEL transmissão 2024: 18 lotes (Teresina IV - Graça Aranha: RAP/ano R$ 112.5M).
- SUSEP empresas: 233 entidades;
search('porto seguro')→ 3 matches (Seguros Gerais, Vida e Previdência, Capitalização).
empresas.pystrips a leading blank line that the live SUSEP CSV ships with — without it,csv.DictReaderinterprets the empty first row as the header and returns 0 records.
Two new sources connecting Dados Financeiros Abertos to Brazil's public-sector accounting + federal-tax flows:
get_rreo / get_rgf / get_entes— Tesouro SICONFI API, the Tesouro Nacional's datalake for the bimonthly RREO (Relatório Resumido de Execução Orçamentária) and quadrimestral RGF (Relatório de Gestão Fiscal) reports filed by every federal, state, and municipal entity in Brazil under the LRF. REST + JSON, public, cached pagination handled transparently (5000-row pages, walks untilhasMore=false). 5,598 entities live (União + 26 UFs + DF + 5,570 municipalities). Filters:cod_ibge,co_anexo,co_poderfor RGF.get_arrecadacao(year=, month=, uf=, tributo=)— Receita Federal monthly tax revenue by UF. Single CSV atgov.br/receitafederal/dados/arrecadacao-estado.csvwith ~45 tax categories (IRPF, IRPJ, IRRF, COFINS, PIS, CSLL, IPI sub-categories, IOF, CIDE, CPSSS, etc.) since 2000. Surfaced as long-form (one row per period × UF × tributo) so callers can pivot without committing to today's column shape (Receita has renamed columns over time).
/tesouro/siconfi/{rreo,rgf,entes}/receita/arrecadacao,/receita/tributos
findata tesouro rreo <COD_IBGE> -y YYYY -b BIMESTRE [-a ANEXO]findata tesouro entes [--uf SP]findata receita arrecadacao -y YYYY [-m MM] [--uf SP] [-t IRPF]
tests/test_tesouro_siconfi.py— 6 respx-mocked tests covering RREO/RGF parsing, pagination follow-up whenhasMore=true,co_anexo/co_poderquery-param passthrough, empty payload, defensivecod_ibge-missing skip on entes.tests/test_receita_arrecadacao.py— 6 respx-mocked tests covering long-form expansion, UF + tributo + year/month filters, empty-cell tolerance, malformed-month skip, and the trailing empty-column header drop (Receita's CSV ends every line with;, yielding an empty-name "column" that csv.DictReader picks up — we filter it out so it doesn't pollutelist_tributos).- 101 unit tests pass (was 89, +12). ruff + ruff-format + mypy --strict clean.
- União 2024-B6 RREO Anexo 06: 6,962 registros (receitas R$ 3.6T previsão inicial / R$ 692B no bimestre).
- SICONFI entes: 5,598 total (União + 26 UFs + DF + 5,570 munis).
- Receita SP 2024-01 IRPF: R$ 1,160,717,531.
- SUSEP open data — site uses
.aspxpages with dropdown-driven downloads; needs more reverse-engineering than this sprint's budget. - ANEEL —
dadosabertos.aneel.gov.brreturned TLS connection-reset during scoping; will retry under the next sprint.
Three CVM products that the regular FI catalog (funds.py) doesn't
cover, each with its own publication path, cadence, and shape:
get_fii_geral / complemento(year, cnpj=None, month=None)— Fundos de Investimento Imobiliário. Annual ZIP with three CSVs (geral,complemento,ativo_passivo); we ship the first two: cadastral facet (segmento, mandato, gestão, administrador) and complement facet (PL, valor patrimonial cota, cotistas breakdown by type — PF / PJ não-financ / banco / EAPC / EFPC / RPPS / etc.).get_fidc_geral / pl / direitos_creditorios(year, month, cnpj=None)— Fundos de Investimento em Direitos Creditórios. Monthly ZIP fans into twelve schedule-CSVs (TAB_I through TAB_X); we expose the three highest-density: TAB_I (cadastral), TAB_IV (PL final + médio), TAB_VII (direitos creditórios com / sem risco + vencidos a adquirir).get_fip(year, cnpj=None, quarter=None, include_raw=False)— Fundos de Investimento em Participações. Annual single CSV (no zip wrapper) with one row per fund per quarter and 54 columns covering capital subscription / integralization, cotistas breakdown, classe de cotas, direitos políticos / econômicos.include_raw=Truecarries the full row for callers that need the long tail.
/cvm/funds/fii/{geral,complemento}/cvm/funds/fidc/{geral,pl,direitos-creditorios}/cvm/funds/fip
findata cvm fii <CNPJ> -y YYYY [-m MM]— segmento/mandato/PL/cotistasfindata cvm fidc <CNPJ> -y YYYY -m MM— classe/PL/direitos creditóriosfindata cvm fip <CNPJ> -y YYYY [-q Q]— capital, cotistas, classe
tests/test_cvm_fii_fidc_fip.py— 9 respx-mocked tests covering FII filter matrix, FIDC TAB_I/IV/VII parsing (Brazilian decimals, ISO-8859-1 encoding), FIP filter + quarter + raw-row preservation.- 87 unit tests pass total (was 78). ruff + ruff-format + mypy --strict clean.
- FII PÁTRIA LOG (
11.728.688/0001-47) 2026-02: PL R$ 7.0B, 544k cotistas, segmento Multicategoria, gestão Ativa. - FIDC fevereiro 2026: 3,736 fundos cadastrados.
- FIP Q4 2023: 2,068 informes trimestrais.
- FIP
_safe_raw()filtersNonekeys fromcsv.DictReaderoutput — pydantic would otherwise reject malformed rows where the data has more delimiters than the header (real CVM data sometimes has trailing empty fields).
Four new high-density public sources, wiring listed-company filings, ticker resolution, and authoritative price history into the catalog:
get_ipe(year, cnpj=None, categoria=None)— CVM IPE event stream (fatos relevantes, comunicados, atas, calendários, boletins de voto). One row per filing with the original PDF link on CVM's RAD system.get_fca_geral / valores_mobiliarios / dri— CVM FCA cadastral form.valores_mobiliariosis a B3-ticker → CNPJ resolver (e.g.ticker="PETR4"→33.000.167/0001-01, segmento, dt_inicio_listagem).geralcarries setor / situação / exercício / website.driis the IR contact card.get_cotahist_year/month/day(year, [m], [d], ticker=None)— B3 COTAHIST official daily-quotes time series (1986+). Fixed-width 245 cols, prices stored in cents, parsed line-by-line so the 85 MB annual file streams without RAM bloat. Filters:ticker,market_codes(CODBDI whitelist —02lote padrão,78/82options, etc.).get_index_portfolio(symbol)— B3 index composition (IBOV, IBrX-50/100, SMLL, IDIV, IFIX, plus 14 sectoral) via theindexProxyJSON endpoint. Returns every constituent with weight (%), share class, and theoretical quantity. Refreshed quarterly.
findata cvm ipe <CNPJ> -y YYYY [--categoria "Fato Relevante"]findata cvm ticker <TICKER> -y YYYY— resolve ticker → company factsfindata b3 cotahist <TICKER> -y YYYY [-m MM] [-d DD]findata b3 index <SYMBOL>— print full theoretical portfolio
/cvm/companies/ipe,/cvm/companies/fca/{geral,securities,dri}/b3/cotahist/{year,month,day}/...,/b3/indices,/b3/indices/{symbol}
Four real bugs caught by a gpt-5.4 xhigh peer review against live
upstream files:
- COTAHIST FATCOT not applied — closed-end funds (FNAM11, FNOR11,
…) quote prices for a lot of FATCOT shares (typically 1000),
affecting 584/2.6M records in COTAHIST_A2024. The parser now divides
by
FATCOTso allpreco_*fields are canonical per-share values.volume_financeirois unchanged (it's already total cash). - B3 indices pagination —
get_index_portfolioonly fetched page 1, silently dropping 22 of ITAG's 222 components. Now loops pages untiltotalPagesis reached (capped at 10 pages = 2000 issues). - NBSP / trailing-whitespace stripping — IPE's free-text fields
(
assunto,tipo_apresentacao, …) carry trailing\xa0in real CVM data. Optional fields now go through a_opt()helper that strips and drops empty (Python's.strip()already treats NBSP as whitespace). Same fix applied to FCA for consistency. get_cotahist_year()RAM bomb — unfiltered annual call would materialise 2.6M Pydantic models. Now requirestickerormarket_codesand raisesValueErrorotherwise. Per-month and per-day calls are unaffected.
tests/test_cvm_ipe_fca.py— 9 respx-mocked tests (IPE filter matrix + NBSP regression + FCA geral/securities/DRI).tests/test_b3_cotahist_indices.py— 12 respx-mocked tests covering fixed-width parser (cents → reais), FATCOT-1000 regression, unfiltered-annual guard, pagination regression, ticker filter, market-code whitelist, base64 round-trip, Brazilian-decimal weight parsing.- 78 unit tests pass (was 57). ruff + ruff-format + mypy --strict clean.
- Vale (
33.592.510/0001-54) IPE 2026: 4 fatos relevantes parsed. - PETR4 FCA 2025 → CNPJ
33.000.167/0001-01, Nível 2 segmento. - IBOV: 83 ativos, top weight VALE3 11.477%, redutor 14.469.518,98.
- PETR4 COTAHIST 2024-03 (lote padrão): 20 sessões, prices in R$.
Three new CVM fund products on top of the existing catalog + daily NAV:
get_fund_holdings(cnpj, year, month)— full portfolio (CDA file). Every position the fund holds, classified by CVM block (BLC_1 títulos públicos, BLC_2 cotas de fundos, BLC_4 ações/debêntures, BLC_8 disponibilidades, etc., plus CONFID / PL / FIE). The monthly zip is ~150 MB unzipped, so a CNPJ filter is required and we line-stream every block in-place. Optionalblocks=whitelist.get_fund_lamina(year, month, cnpj=None)plusget_fund_monthly_returns()andget_fund_yearly_returns()— the regulatory factsheet (lâmina) with strategy, restrições, alavancagem caps, plus per-month and per-year returns vs benchmark.get_fund_profile(year, month, cnpj=None)— perfil mensal: cotistas count broken down by type (PF private/varejo, PJ financeira/não-financeira, banco, corretora).
findata.sources.cvm.list_files / list_periods / latest_period— scrapeshttps://dados.cvm.gov.br/dados/<cat>/<product>/DADOS/directly so we don't hard-code year ranges that go stale. New/cvm/funds/periods?product=...route exposes it.- Pattern lifted from
gabrielguarisa/brdata's_get_table_links, reimplemented async-native against our shared httpx client.
findata cvm holdings <CNPJ> -y YYYY -m MM [--blocks BLC_1,BLC_4]findata cvm lamina <CNPJ> -y YYYY -m MMfindata cvm profile <CNPJ> -y YYYY -m MM
tests/test_cvm_funds.py— 9 respx-mocked tests covering directory listing, block-label decoding, CDA filter + whitelist, lâmina main + monthly + yearly returns, perfil filter + no-filter passthrough.- 57 unit tests pass total (was 48). ruff + mypy --strict clean.
Validated end-to-end against real CVM with 00.280.302/0001-60
(Bradesco H FIF — Crédito Privado): 6 holdings (R$ 270M concentrados em
cotas de outros fundos), lâmina com objetivo CDI + 0% alavancagem, 1491
cotistas PF varejo + 50 PJ varejo + 24 PF private.
findata.sources.anbima— covers IMA family snapshot (IRF-M, IMA-B, IMA-S, IMA-Geral, with sub-indices likeIRF-M 1+,IMA-B 5, etc.), the ETTJ zero-coupon yield curve (Pré, IPCA, inflação implícita per vértice), and daily debentures secondary-market quotes.- Implementation reads ANBIMA's free static files at
www.anbima.com.br/informacoes/*(XLS / CSV / TXT). No credentials, no API keys, no cadastro institucional — same canonical numbers as ANBIMA's commercial Sensedia API, just delivered as files. - Rotas:
/anbima/{ima,ettj,debentures}, all public. - CLI:
findata anbima {ima,ettj,debentures}(with--family,--date,--emissorfilters). xlrd>=2.0.1added as a core dependency to parse the legacy.xlsfiles ANBIMA still publishes.
findata.authmodule — genericOAuth2ClientCredentialsflow with in-process token cache and customisable header conventions for non-spec gateways (Sensedia, etc.). SurfacesAuthErrorandMissingCredentialsErrorfor callers.- The framework is intentionally unused by the current ANBIMA integration
(which is fully public). It stays in the codebase for future sources
that genuinely require auth — SUSEP, BNDES, CETIP, etc. The recipe for
contributors is documented in
docs/SOURCES_WITH_AUTH.md.
We initially shipped ANBIMA as an authenticated integration, validated the OAuth2 flow live, but discovered ANBIMA's developer programme isn't self-serve — it requires institutional membership or a commercial contract. We pivoted to the equivalent public file feeds, which give the same data without gating. The auth scaffolding stays so the next genuinely credentialed source ships in hours rather than days.
- Rate limiting via
slowapi—FINDATA_RATE_LIMIT_DEFAULTenv var (default60/minute;1000/day), keyed byX-Forwarded-Forthen remote address. EmitsX-RateLimit-Limit/Remaining/Resetheaders. Toggle withFINDATA_RATE_LIMIT_ENABLED=falsefor local dev. GET /statsendpoint — uptime, cache size, sources, MCP enabled, rate-limit state. Cheap to poll from status pages.deploy/findata-br.service— hardened systemd unit (NoNewPrivileges, ProtectSystem=strict, MemoryMax=512M).deploy/docker-compose.prod.yml— localhost-only binding + optionalcloudflaredsidecar behind a--profile tunnelflag.docs/DEPLOY_PUBLIC.md— full walkthrough (pt-BR) to stand the server up on Windows+WSL2 behind Cloudflare Tunnel for free.- Community scaffolding — issue templates (
new-source,bug) with aconfig.ymlthat links to Discussions + the deploy guide, plusCODE_OF_CONDUCT.md(Contributor Covenant 2.1).
- Extract
findata._cache.TTLCache, eliminating three copy-pasted module-level caches (_companies/_companies_at/_CACHE_TTLin CVM companies,_catalog/_catalog_at/_CATALOG_TTLin CVM funds,_parsed/_parsed_at/_PARSED_TTLin Tesouro bonds) and theglobalkeyword dance that went with them. - Extract
findata._odata.parse_odata— the single generic ODatavalue[]→ Pydantic parser that lived inbcb/focus.py. BCB PTAX'sget_ptax_*andget_currenciesnow use it too, dropping an ad-hoc_parse_quotesand a hand-rolled dict-unpacking list comprehension. - Lazy B3 thread-pool —
ThreadPoolExecutoris created on first use and drained byclose_executor(), wired into the FastAPI lifespan. Importing the module no longer costs a live pool. - Type-safe CLI
_run— now_run(coro: Coroutine[Any, Any, T]) -> Tvia aTypeVar, sotyperhandlers don't leakAnyto callers. - Pin
fastapi-mcp>=0.4and drop the defensivemount()fallback — the current API ismount_http()and the fallback was dead code. - Docstrings added to previously empty
__init__.pyfiles (api/,api/routers/,sources/). - Silence upstream
PydanticDeprecatedSince211warning at the pytest level — it's triggered from inside pydantic-core when we validate aConfigDictand not something our code can fix.
- IPEA Data source (OData v4) — access to ~8k curated macroeconomic
series with endpoints for catalog listing, by-SERCODIGO values,
metadata, and full-text search. CLI:
findata ipea {catalog,get,search}. - IPEA router at
/ipea/*and curated built-in catalog. - Git guardrails for Python formatting, lint, types, tests, and security:
- Ruff now enforces AI guardrails (complexity
C901, max-argsPLR0913, magic-valuePLR2004, branchesPLR0912, returnsPLR0911, stmtsPLR0915), flake8-banditS*,no-printT201,ERAcommented-out. - Per-context overrides for tests (ignore
S/PLR2004) and FastAPI routers (ignoreB008). .githooks/pre-commit(ruff + format check on staged .py + optionalggshieldsecret scan) and.githooks/pre-push(fullruff check+mypy --strict+pytest). Installed viabash scripts/git/install-hooks.sh→core.hooksPath = .githooks.
- Ruff now enforces AI guardrails (complexity
CONTRIBUTING.mddocumenting the guardrail philosophy and dev workflow.ROADMAP.mdsection comparing Dados Financeiros Abertos against Tpessia/dados-financeiros and gprossignoli/findata (lessons learned, what we adopt / skip).
- README fully translated to pt-BR and the top banner now renders as an
ASCII code block (
DADOS FINANCEIROS ABERTOS), matching CLI-tool repo conventions instead of relying on a remote typing-svg image that some GitHub clients were failing to render. - Badges trimmed to working ones (CI, version, Python, license, status) — removed PyPI/Python-version badges that returned 404 while the package is unpublished.
- Initial public release of Dados Financeiros Abertos.
- REST API (FastAPI) with routers for BCB, CVM, B3, IBGE, Tesouro.
- Auto-generated MCP server mounted at
/mcp(viafastapi-mcp). - CLI (
findata) with animated gradient banner and rich-text tables. - Async Python library with shared httpx client (connection pool, retries, LRU cache, OData-safe URL builder).
- BCB sources: SGS (18 curated series), PTAX (USD/EUR/all currencies, period endpoint), Focus (annual, monthly, Selic-per-COPOM, Top-5).
- CVM sources: registered companies (search + listing), DFP/ITR financial statements, fund catalog and daily NAV.
- IBGE Agregados v3 source with IPCA breakdown by 10 major groups.
- Tesouro Transparente historical bond CSV with caching.
- B3 quotes via
yfinance(optional extra[b3]). - Unit tests for http client, models, parsers, and API (network-free, via
respx). Integration suite gated bypytest -m integration. Dockerfile+docker-compose.ymlfor one-command deployment.- GitHub Actions CI matrix (3.11/3.12/3.13).