Bot de Telegram para cotações da B3 em Python (2026)
por bolsai · 2 de julho de 2026 · 11 min de leitura
Construir um bot telegram cotações B3 python é um dos projetos mais gratificantes para quem está
aprendendo a integrar código com o mercado brasileiro: em poucas dezenas de linhas você digita
/cotacao PETR4 no celular e recebe o fechamento do papel de volta. Este tutorial
mostra o caminho completo, do registro do bot no BotFather até um handler que consulta a API da
bolsai e formata a resposta, com comandos para cotação e para indicadores fundamentalistas. Uma
ressalva importante desde já: os dados são de fechamento do pregão, atualizados uma vez por dia,
não em tempo real.
O que o bot vai fazer
Como criar um bot de Telegram para cotações da B3 em Python?
Registre o bot com o BotFather para obter um token, instale a biblioteca
python-telegram-bot, crie uma chave gratuita na bolsai e escreva handlers de comando
que chamam GET /stocks/{ticker}/quote e GET /fundamentals/{ticker}. O
comando /cotacao PETR4 devolve o fechamento do último pregão; /fundamentos VALE3
devolve P/L, P/VP e ROE. Os dados são de fechamento, atualizados diariamente por volta das 20h30.
O objetivo é um bot pessoal que responde a dois comandos principais. O primeiro,
/cotacao PETR4, retorna abertura, máxima, mínima, fechamento e volume do último pregão. O
segundo, /fundamentos VALE3, retorna os principais indicadores fundamentalistas do papel,
como preço sobre lucro e retorno sobre patrimônio. O mesmo bot serve para ações, units e fundos
imobiliários, porque todos compartilham a estrutura de tickers da B3.
A arquitetura tem três peças. O Telegram fornece a interface de chat e entrega as mensagens do usuário
ao seu programa via long polling. A biblioteca python-telegram-bot cuida desse canal e
roteia cada comando para uma função Python. A
bolsai fornece os dados: o seu
código só precisa montar a URL certa, enviar a chave de API e formatar o JSON de volta em texto legível.
Passo 1: registrar o bot no BotFather
Todo bot de Telegram nasce de uma conversa com o
BotFather,
o bot oficial que cria outros bots. No aplicativo do Telegram, procure por @BotFather,
inicie a conversa e envie o comando /newbot. Ele pede um nome de exibição e um nome de
usuário terminado em bot, por exemplo minha_carteira_b3_bot. Ao final, o
BotFather devolve um token no formato 123456789:ABCdef....
Esse token é a credencial do bot: qualquer pessoa que o tenha controla o bot. Guarde-o com o mesmo
cuidado de uma senha. Neste tutorial ele vai para uma variável de ambiente, nunca escrito diretamente
no código. Aproveite ainda para enviar /setcommands ao BotFather e cadastrar a lista de
comandos, o que faz o Telegram mostrar um menu de sugestões quando o usuário digita a barra:
cotacao - Cotação de fechamento de um ticker (ex: /cotacao PETR4)
fundamentos - Indicadores fundamentalistas (ex: /fundamentos VALE3)
Passo 2: chave da bolsai e instalação
Os dados vêm da bolsai, que ingere o arquivo COTAHIST oficial da B3 e expõe cada ticker em um endpoint
REST. A criação da conta é gratuita via login Google no
dashboard, e a chave aparece assim que o
cadastro termina. O plano gratuito libera 200 requisições por dia, folga confortável para um bot
pessoal ou um grupo pequeno. A autenticação usa o header X-API-Key em cada chamada.
As dependências são duas: a biblioteca do Telegram e um cliente HTTP. A
python-telegram-bot
é a mais usada e madura do ecossistema; a partir da versão 20 ela roda sobre asyncio, e a
série 22.x atual exige Python 3.10 ou superior. Para as chamadas à bolsai, este tutorial usa
requests, que é síncrono e suficiente para o volume de um bot pessoal:
pip install python-telegram-bot requests
Antes de mexer no bot, vale confirmar que a chave funciona com uma consulta isolada. O trecho abaixo é
autocontido: troque sk_sua_chave_aqui pela sua chave real e execute. Ele bate no endpoint
/stocks/{ticker}/quote e imprime o fechamento:
import requests
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_sua_chave_aqui"}
r = requests.get(f"{BASE}/stocks/PETR4/quote", headers=HEADERS, timeout=10)
r.raise_for_status()
q = r.json()
print(f"{q['ticker']} fechou a R$ {q['close']:.2f} em {q['trade_date']}")
print(f"Abertura R$ {q['open']:.2f} | Máx R$ {q['high']:.2f} | Mín R$ {q['low']:.2f}")
print(f"Volume: {q['volume']:,} ações")
A resposta do endpoint tem o formato abaixo. Os números aqui são apenas ilustrativos, para mostrar o shape do JSON; a sua consulta traz os valores reais do último pregão disponível:
O raise_for_status() transforma respostas de erro (401 para chave inválida, 404 para
ticker inexistente, 429 quando a cota do dia acaba) em exceções Python, evitando tratar um erro como
JSON válido. Confirmada a consulta, o próximo passo é embrulhar isso em handlers do Telegram. A
documentação da bolsai lista todos os campos
de cada endpoint.
Passo 3: o esqueleto do bot
A biblioteca python-telegram-bot gira em torno de um objeto Application,
construído com ApplicationBuilder. Você registra handlers, cada um ligando um comando a
uma função assíncrona, e chama run_polling() para o bot começar a escutar. O esqueleto
mínimo, com um comando /start de boas-vindas, fica assim:
import os
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
TELEGRAM_TOKEN = os.environ["TELEGRAM_TOKEN"]
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text(
"Olá! Envie /cotacao PETR4 para o fechamento "
"ou /fundamentos VALE3 para os indicadores."
)
def main() -> None:
app = ApplicationBuilder().token(TELEGRAM_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.run_polling()
if __name__ == "__main__":
main()
Cada handler recebe dois argumentos: update, que carrega a mensagem e o usuário, e
context, que dá acesso a argumentos do comando e a utilitários. Para responder, basta
chamar update.message.reply_text(...). O token sai de os.environ, então antes
de rodar exporte a variável no terminal:
export TELEGRAM_TOKEN="123456789:ABCdef..."
export BOLSAI_API_KEY="sk_sua_chave_aqui"
python bot.py
Ainda não tem uma chave da bolsai? A conta gratuita libera 200 requisições por dia, mais do que suficiente para um bot pessoal. Crie a sua em segundos com login Google e comece a testar os comandos hoje mesmo.
Criar chave gratuitaPasso 4: o comando /cotacao
Agora o comando que dá sentido ao bot. Quando o usuário envia /cotacao PETR4, a biblioteca
separa tudo o que vem depois da barra e do nome do comando em context.args, uma lista de
palavras. Para /cotacao PETR4, context.args é ["PETR4"]. O handler
lê o primeiro elemento, valida que existe, consulta a bolsai e formata a resposta.
Vale isolar a chamada à API em uma função própria, separando a lógica de dados da lógica de chat. Isso deixa o código testável e permite reutilizar a mesma função no comando de fundamentos:
import os
import requests
BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": os.environ["BOLSAI_API_KEY"]}
def buscar_cotacao(ticker: str) -> dict:
"""Consulta o fechamento do último pregão de um ticker."""
url = f"{BASE}/stocks/{ticker}/quote"
r = requests.get(url, headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()
Com a função de dados pronta, o handler do comando fica enxuto. Ele trata três situações: comando sem ticker, ticker inexistente (404 da API) e a resposta feliz. As chamadas de rede são embrulhadas para que uma falha nunca derrube o bot inteiro, apenas devolva uma mensagem de erro ao usuário:
from telegram import Update
from telegram.ext import ContextTypes
from requests.exceptions import HTTPError, RequestException
async def cotacao(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not context.args:
await update.message.reply_text("Uso: /cotacao PETR4")
return
ticker = context.args[0].upper()
try:
q = buscar_cotacao(ticker)
except HTTPError as e:
if e.response.status_code == 404:
await update.message.reply_text(f"Ticker {ticker} não encontrado na B3.")
elif e.response.status_code == 429:
await update.message.reply_text("Cota diária esgotada. Tente amanhã.")
else:
await update.message.reply_text("Erro ao consultar a cotação.")
return
except RequestException:
await update.message.reply_text("Serviço indisponível no momento.")
return
msg = (
f"*{q['ticker']}* | fechamento de {q['trade_date']}\n\n"
f"Fechamento: R$ {q['close']:.2f}\n"
f"Abertura: R$ {q['open']:.2f}\n"
f"Máxima: R$ {q['high']:.2f}\n"
f"Mínima: R$ {q['low']:.2f}\n"
f"Volume: {q['volume']:,} ações\n\n"
"_Dado de fechamento, não intradiário._"
)
await update.message.reply_text(msg, parse_mode="Markdown")
O parse_mode="Markdown" permite negrito e itálico na mensagem, o que dá acabamento à
resposta. O ticker é normalizado com .upper() porque o usuário pode digitar em minúsculas,
e a bolsai aceita tickers sem diferenciar maiúsculas de minúsculas. A linha final deixa explícito que o
número é de fechamento, algo que evita mal-entendidos quando alguém consulta o bot durante o pregão e
estranha o valor não bater com a corretora.
Passo 5: o comando /fundamentos
O comando de fundamentos segue o mesmo molde, trocando o endpoint por
/fundamentals/{ticker}. Esse endpoint devolve os indicadores fundamentalistas calculados a
partir dos dados DFP e ITR mais recentes que a empresa publicou na CVM. Entre os campos estão
pl (preço sobre lucro), pvp (preço sobre valor patrimonial),
roe (retorno sobre patrimônio) e net_margin (margem líquida). A função de
dados é análoga à da cotação:
def buscar_fundamentos(ticker: str) -> dict:
"""Indicadores fundamentalistas a partir do último balanço."""
url = f"{BASE}/fundamentals/{ticker}"
r = requests.get(url, headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()
async def fundamentos(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not context.args:
await update.message.reply_text("Uso: /fundamentos VALE3")
return
ticker = context.args[0].upper()
try:
f = buscar_fundamentos(ticker)
except HTTPError as e:
if e.response.status_code == 404:
await update.message.reply_text(f"Sem fundamentos para {ticker}.")
else:
await update.message.reply_text("Erro ao consultar fundamentos.")
return
except RequestException:
await update.message.reply_text("Serviço indisponível no momento.")
return
def fmt(v): # alguns indicadores podem vir nulos
return f"{v:.2f}" if isinstance(v, (int, float)) else "n/d"
msg = (
f"*{f['ticker']}* | balanço de {f['reference_date']}\n\n"
f"P/L: {fmt(f.get('pl'))}\n"
f"P/VP: {fmt(f.get('pvp'))}\n"
f"ROE: {fmt(f.get('roe'))}%\n"
f"Margem líquida: {fmt(f.get('net_margin'))}%\n"
f"EV/EBITDA: {fmt(f.get('ev_ebitda'))}"
)
await update.message.reply_text(msg, parse_mode="Markdown")
A função auxiliar fmt existe porque nem todo indicador está disponível para todo papel.
Empresas com poucos anos de histórico podem não ter CAGR de cinco anos, por exemplo, e alguns campos de
bancos seguem metodologia diferente. Tratar valores nulos com um "n/d" em vez de deixar o
.2f quebrar mantém o bot resiliente. Quem quiser entender como cada número é calculado
encontra o detalhamento no
tutorial de consulta de cotações em Python,
que cobre o mesmo padrão de chamada aplicado a séries maiores.
Passo 6: juntando tudo
Com as três funções de handler prontas (start, cotacao e
fundamentos) e as duas funções de dados, o main registra cada comando e sobe o
bot. O arquivo final bot.py tem esta estrutura de montagem:
def main() -> None:
app = ApplicationBuilder().token(TELEGRAM_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("cotacao", cotacao))
app.add_handler(CommandHandler("fundamentos", fundamentos))
print("Bot no ar. Ctrl+C para parar.")
app.run_polling()
if __name__ == "__main__":
main()
Ao rodar python bot.py, o processo fica escutando o Telegram por long polling. Abra a
conversa com o seu bot no aplicativo, envie /cotacao ITUB4 e a resposta chega em segundos.
No chat, o resultado renderizado fica parecido com isto (valores ilustrativos):
O mesmo handler resolve fundos imobiliários sem nenhuma mudança. Enviar /cotacao HGLG11 ou
/cotacao MXRF11 passa pelo mesmo endpoint /stocks/{ticker}/quote, que aceita o
sufixo 11 dos FIIs e devolve o mesmo payload de preços e volume.
Rate limit e cache: economizando as 200 chamadas do dia
O plano gratuito da bolsai permite 200 requisições por dia, com reinício às 00:00 UTC. Cada comando que bate na API consome uma chamada. Para um bot pessoal isso raramente é um problema, mas dois hábitos estendem bastante o orçamento. O primeiro é o cache: como a cotação de fechamento não muda ao longo do dia útil, consultar o mesmo ticker cinco vezes desperdiça quatro chamadas. Um cache simples em memória, válido por algumas horas, resolve:
import time
_cache: dict = {}
TTL = 3600 # segundos: 1 hora
def buscar_cotacao_cache(ticker: str) -> dict:
agora = time.time()
if ticker in _cache:
dado, ts = _cache[ticker]
if agora - ts < TTL:
return dado # cache hit: não gasta requisição
dado = buscar_cotacao(ticker)
_cache[ticker] = (dado, agora)
return dado
O segundo hábito é acompanhar o consumo. A bolsai expõe o endpoint
/keys/usage, que devolve quanto da cota diária já foi usado. Um bot que atende um grupo
pode consultar esse número periodicamente e avisar antes de esbarrar no limite. Se o volume crescer além
do plano gratuito, o plano Pro sobe para 10.000 requisições diárias por 29 reais mensais, o que absorve
um bot bem movimentado com folga. A própria bolsai ainda aplica cache de cinco minutos no lado do
servidor, então consultas repetidas ao mesmo ticker respondem em milissegundos.
Do bot de comandos ao agente inteligente
O bot deste tutorial responde a comandos fixos, um modelo previsível e barato. Um passo natural adiante é permitir perguntas em linguagem natural, do tipo "compare PETR4 e PRIO3" ou "quais os fundamentos da WEGE3", e deixar um modelo de linguagem decidir quais dados buscar. A bolsai oferece um servidor MCP com ferramentas prontas para esse cenário, cobertas no guia de agente de IA para monitorar carteira. Ali o modelo chama as mesmas fontes de dados sem que você escreva um handler por comando.
Outra evolução comum é o disparo proativo: em vez de esperar o usuário perguntar, o bot avisa quando um papel atinge um preço-alvo ou quando um novo balanço é publicado. Esse tipo de fluxo agendado combina bem com ferramentas de automação visual, tema do tutorial de automação de alertas de ações com n8n, que orquestra a consulta periódica à bolsai e o envio da notificação sem servidor dedicado.
Perguntas frequentes
O bot responde com cotação em tempo real?
Não. O bot responde com a cotação de fechamento do último pregão. A bolsai processa o arquivo COTAHIST da B3 uma vez por dia útil e disponibiliza o fechamento por volta das 20h30 de Brasília. Não há preço intradiário nem book de ofertas ao vivo. Para acompanhamento de carteira, alertas diários e consultas de fundamentos, o fechamento D-0 atende. Se o seu caso exige ticks em tempo real, é preciso um provedor especializado em feed ao vivo, que é outra categoria de serviço.
Quantas consultas o bot consegue fazer por dia?
O plano gratuito da bolsai permite 200 requisições por dia, e o limite reinicia às 00:00 UTC. Cada comando que consulta a API consome uma requisição. Para um bot pessoal ou um grupo pequeno de amigos, 200 chamadas diárias costumam sobrar, especialmente com um cache local que evita repetir a mesma consulta. Se o bot atende muitos usuários, o plano Pro sobe para 10.000 requisições por dia por 29 reais mensais.
Qual biblioteca Python usar para o bot de Telegram?
A
python-telegram-bot
é a biblioteca mais usada e madura. A partir da versão 20 ela é construída sobre asyncio, e
a API atual (série 22.x) usa ApplicationBuilder, CommandHandler e
run_polling. Requer Python 3.10 ou superior. Ela cuida do long polling com a API do
Telegram, do roteamento de comandos e do envio de respostas, então o seu código foca apenas em consultar
a bolsai e formatar a mensagem.
O bot funciona com FIIs além de ações?
Sim. Fundos imobiliários usam a mesma estrutura de tickers da B3, terminados em 11, como HGLG11 e
MXRF11. O endpoint /stocks/{ticker}/quote aceita esses códigos e devolve o mesmo payload de
abertura, máxima, mínima, fechamento e volume. Basta enviar /cotacao HGLG11 que o mesmo
handler resolve. Para indicadores específicos de FIIs, como P/VP e dividend yield mensal, a bolsai tem o
endpoint dedicado /fiis/{ticker}.
É seguro deixar o token do bot no código?
Não. Tanto o token do bot do Telegram quanto a API key da bolsai devem ficar em variáveis de ambiente,
nunca escritos no arquivo Python que vai para um repositório. Qualquer pessoa com o token do bot
controla o bot, e qualquer pessoa com a API key consome a sua cota. O padrão é ler os dois de
os.environ e carregar de um arquivo .env que fica fora do controle de versão.
Comece agora
Um bot telegram cotações B3 python sai de zero a funcional em uma tarde: registro no BotFather,
instalação da python-telegram-bot, uma chave gratuita da bolsai e dois handlers que
consultam /stocks/{ticker}/quote e /fundamentals/{ticker}. Os exemplos deste
tutorial, do esqueleto ao tratamento de erros e ao cache, cobrem o que um bot pessoal precisa para
responder /cotacao PETR4 e /fundamentos VALE3 com segurança. Lembre sempre da
natureza do dado: é o fechamento do pregão, atualizado uma vez por dia por volta das 20h30, não uma
cotação ao vivo. O plano gratuito com 200 requisições por dia valida a ideia inteira antes de qualquer
upgrade.
Leia também
Este conteúdo é educativo e não constitui recomendação de investimento. Os dados de cotação são de fechamento do pregão, atualizados uma vez por dia útil, e não substituem uma fonte de preços em tempo real. Performance passada não garante resultados futuros.