Tutorial 2 jul 2026 11 min de leitura

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:

{ "ticker": "PETR4", "trade_date": "2026-07-01", "open": 45.12, "high": 46.03, "low": 44.89, "close": 45.67, "volume": 42318700, "traded_amount": 1932045876.54, "num_trades": 187432 }

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 gratuita

Passo 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):

ITUB4 | fechamento de 2026-07-01 Fechamento: R$ 34.52 Abertura: R$ 34.20 Máxima: R$ 34.80 Mínima: R$ 34.05 Volume: 24,563,100 ações Dado de fechamento, não intradiário.

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.