PT

Dados B3Guides › Python

Guide · Python

A B3 stock screener in Python, without looking into the future

The most common mistake when testing an investment idea on financial statements is using a number that did not exist yet on the test date. The 2018 statement only reaches the regulator in 2019 — on average 100 days after year end. This guide screens stocks by what was public on each date, and keeps the result in a form someone else can verify.

Install

pip install dadosb3

The screen covers the whole universe, so it needs a key — the free one works (200 queries a day, no card). Put it in DADOS_B3_API_KEY or pass it to the constructor.

Two different questions: ano and as_of

from dadosb3 import DadosB3

api = DadosB3()   # reads DADOS_B3_API_KEY

# "who had ROIC above 15% IN financial year 2018"
by_year = api.screener(roic_min=0.15, ano=2018)

# "what could I have picked on 2019-06-30"
by_date = api.screener(roic_min=0.15, dl_ebitda_max=2, as_of="2019-06-30")
for e in by_date["empresas"]:
    print(e["ticker"], e["ano"], e["disponivel_em"], e["indicadores"]["roic"])

The first answers a question nobody could answer in 2018. The second uses, for each company, the latest year whose statement had already reached the regulator on that date — and each row carries disponivel_em so you can check. Indicators are fractions: 15% ROIC is 0.15. An invalid filter name is rejected with an error, never ignored; the full menu is at /screener.

Keeping the answer: the receipt

Next week, after the database updates, the same question may return another list. To cite today's, ask for a receipt:

r = api.screener(roic_min=0.15, as_of="2019-06-30", recibo=1)
print(r["recibo"]["url"])      # permanent address
print(r["recibo"]["sha256"])   # fingerprint of the result

The receipt stores the question, the data version and the result, and opens without a key — whoever gets the link can check it. The same question on the same version returns the same receipt. To verify the fingerprint:

import hashlib, json, urllib.request
rec = json.load(urllib.request.urlopen(r["recibo"]["json"]))
body = json.dumps(rec["resultado"], sort_keys=True, separators=(",", ":"),
                  ensure_ascii=False).encode()
assert hashlib.sha256(body).hexdigest() == rec["sha256"]

The limit this does not fix

The as-of date removes the future from the numbers, not from the universe: the database holds the companies active today. A 2019 query picks only among those that survived until now — survivorship bias. The response says so in limite_conhecido, and the size of the hole is measured company by company at /saiu-da-bolsa. That is why we do not publish portfolio backtests: with only 11% of the companies that left reconstructible, the result would mislead.

Sources: CVM (open data, ODbL) and B3 (COTAHIST). Not affiliated with B3 or the CVM. Not investment advice.

Numbers on this page are live. Data version 2026-09-14-etfs · page generated 2026-09-23 00:39 UTC. If this does not match /saude, you are reading a cached copy.