Tutorial por bolsai 2 jul 2026 12 min de leitura

Como criar alertas de preço de ações em Python (2026)

por bolsai · 2 de julho de 2026 · 12 min de leitura

Montar alertas de preço de ações python é um dos primeiros projetos práticos de quem quer parar de abrir o home broker várias vezes ao dia só para checar se um papel bateu um preço. A ideia é simples: um script consulta a cotação, compara com um gatilho que você definiu e dispara uma notificação por e-mail ou Telegram quando a condição é atingida. Este tutorial mostra o caminho completo em Python, com um detalhe honesto de partida que muda tudo no desenho da solução: a base de dados usada aqui é a cotação de fechamento, atualizada uma vez por dia útil. Isso torna o projeto ideal para swing trade e position trade, e inadequado para day trade.

O que é um alerta de preço e o que este tutorial entrega

Como criar um alerta de preço de ação em Python? Um script agendado consulta a cotação via API REST, compara o preço de fechamento com um gatilho (por exemplo "avisar se PETR4 fechar abaixo de R$ 30") e, quando a condição é verdadeira, envia uma mensagem por e-mail ou Telegram. A consulta usa GET /stocks/{ticker}/quote com o header X-API-Key. Como o dado é de fechamento, o alerta dispara depois do pregão, o que atende swing trade e position trade, não day trade.

Um alerta de preço tem três partes independentes. A primeira é a fonte de dados: de onde vem o preço que será comparado. A segunda é a regra: a condição lógica que define quando avisar. A terceira é o canal de notificação: como a mensagem chega até você. Este tutorial resolve as três, mas dá ênfase à primeira, porque a natureza da fonte determina o tipo de alerta que é honesto prometer. Uma fonte de fechamento diário gera alertas de fim de pregão; uma fonte intradiária geraria alertas em tempo real. São produtos diferentes, e confundir os dois leva a expectativas erradas.

A bolsai ingere o arquivo COTAHIST oficial da B3 diariamente e expõe a cotação de cada ticker em um endpoint único. Ações, units e FIIs usam a mesma estrutura de resposta. Nos exemplos a seguir, os números são ilustrativos, escolhidos apenas para demonstrar o formato da resposta e a lógica de comparação; os valores reais você obtém rodando o código contra a sua conta.

Antes de tudo: estes são alertas de fechamento, não intradiários

Ponto crítico de honestidade. A cotação servida pela bolsai é de fechamento, atualizada uma vez por dia útil, por volta das 20h30 de Brasília, depois do encerramento do pregão regular da B3. Um alerta construído sobre esse dado avisa quando o fechamento do dia cruzou o seu gatilho. Ele não avisa no instante em que o preço se move durante o pregão. Não é um alerta em tempo real nem intradiário.

Essa característica não é uma limitação a esconder, é o que define o público certo do projeto. Quem opera em horizonte de dias, semanas ou meses toma decisões olhando o gráfico diário e o fechamento. Para esse investidor, saber que um papel fechou abaixo do preço-teto de compra, ou que rompeu uma resistência no candle diário, é exatamente a informação relevante, e ela só existe depois que o pregão fecha. Um alerta que chega às 21h avisando sobre o fechamento do dia é oportuno para montar ou ajustar posição no pregão seguinte.

Quem faz day trade ou scalping, ao contrário, precisa reagir a movimentos de segundos ou minutos dentro do pregão. Para esse perfil, a cotação de fechamento é inútil como gatilho de alerta, e a ferramenta correta é um feed de cotação ao vivo com preço de book e ticks intradiários, oferecido por vendors especializados. Este tutorial é explícito nesse recorte: ele serve swing trader, investidor de dividendos e quem monta posição de forma gradual, e não serve day trader. Assumir isso desde o começo evita construir algo que promete o que a fonte de dados não pode cumprir.

Perfil Horizonte Alerta de fechamento serve?
Position / buy and hold Meses a anos Sim, com folga
Swing trade Dias a semanas Sim, base no gráfico diário
Montagem gradual de posição Semanas a meses Sim, avisa faixa de compra
Day trade / scalping Segundos a horas Não, exige dado intradiário

A arquitetura de um script de alerta

O fluxo de qualquer alerta de preço cabe em quatro passos, executados em sequência a cada rodada:

  1. Consultar a cotação de fechamento de cada ticker monitorado.
  2. Avaliar cada gatilho contra o preço retornado.
  3. Notificar apenas os gatilhos que dispararam.
  4. Registrar o que já foi avisado, para não repetir a mesma mensagem todo dia.

O quarto passo costuma ser esquecido e é o que separa um script tolerável de um irritante. Sem controle de estado, um papel que ficou abaixo do gatilho por duas semanas gera catorze mensagens idênticas. O padrão saudável é avisar na transição, ou seja, apenas quando a condição passa de falsa para verdadeira, e voltar a armar o alerta quando o preço sai da zona. O caminho aqui é montar o núcleo primeiro e adicionar o controle de repetição no final.

Setup: conta gratuita, API key e dependências

A autenticação da bolsai funciona por chave enviada no header X-API-Key. A criação da conta é gratuita via login Google no dashboard, e a chave aparece logo após o cadastro. O plano gratuito libera 200 requisições por dia, o que cobre com sobra um monitor de carteira pessoal: dez papéis checados uma vez ao dia consomem dez requisições. A documentação completa lista todos os endpoints, campos e limites.

O único requisito obrigatório para a parte de dados é a biblioteca requests. As demais dependências dependem do canal de notificação escolhido; o envio por e-mail usa apenas a biblioteca padrão do Python.

pip install requests

O endpoint de cotação retorna um payload enxuto. Os campos relevantes para alertas estão na tabela abaixo, alinhados ao schema exposto pela API.

Campo Tipo Descrição
ticker string Código do papel na B3 (ex.: PETR4, VALE3, ITUB4).
trade_date date (ISO) Data do pregão a que o fechamento se refere, no formato YYYY-MM-DD.
open number Preço de abertura do pregão em reais.
high number Máxima do dia em reais.
low number Mínima do dia em reais.
close number Preço de fechamento em reais. É o campo usado como referência dos gatilhos.
volume integer Quantidade total de ações negociadas no pregão.
traded_amount number Volume financeiro total em reais.
num_trades integer Número de negócios executados no pregão.

Consultando a cotação de fechamento

O primeiro bloco é a função que obtém a cotação. Ela chama GET /stocks/{ticker}/quote, trata erros de HTTP e devolve o dicionário do payload. O raise_for_status() transforma respostas de erro (401, 404, 429, 5xx) em exceções, evitando que um corpo de erro seja tratado como cotação válida.

import requests

BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_sua_chave_aqui"}

def cotacao_fechamento(ticker):
    r = requests.get(f"{BASE}/stocks/{ticker}/quote", headers=HEADERS, timeout=10)
    r.raise_for_status()
    return r.json()

q = cotacao_fechamento("PETR4")
print(f"{q['ticker']} fechou a R$ {q['close']:.2f} em {q['trade_date']}")
PETR4 fechou a R$ 30.42 em 2026-07-01

O valor acima é ilustrativo. O que importa é o formato: o campo close traz o preço de fechamento em reais e trade_date informa a qual pregão ele se refere. Sempre confira o trade_date antes de disparar um alerta. Se o script rodar antes das 20h30, ou em um dia sem pregão, o trade_date será de uma sessão anterior, e comparar um gatilho com um fechamento defasado gera aviso enganoso. Checar a data é a defesa mais barata contra alertas falsos.

Definindo os gatilhos e avaliando as regras

Um gatilho é uma condição sobre o preço de fechamento. Os dois casos mais comuns são "avisar quando cair abaixo de X" (típico de compra) e "avisar quando subir acima de Y" (típico de realização ou rompimento). Representar cada gatilho como um pequeno dicionário mantém a configuração legível e fácil de estender:

# Cada alerta: ticker, direção ("abaixo" ou "acima") e o preço-limite.
ALERTAS = [
    {"ticker": "PETR4", "direcao": "abaixo", "limite": 30.00},
    {"ticker": "VALE3", "direcao": "abaixo", "limite": 55.00},
    {"ticker": "ITUB4", "direcao": "acima", "limite": 38.00},
]

def disparou(direcao, close, limite):
    if direcao == "abaixo":
        return close <= limite
    if direcao == "acima":
        return close >= limite
    raise ValueError(f"direção inválida: {direcao}")

Com a função de consulta e a de avaliação prontas, o laço principal percorre a lista, obtém a cotação de cada papel e monta a lista de disparos. Repare que o preço comparado é sempre o close, o fechamento do pregão indicado em trade_date:

def checar_alertas():
    disparos = []
    for a in ALERTAS:
        q = cotacao_fechamento(a["ticker"])
        close = q["close"]
        if disparou(a["direcao"], close, a["limite"]):
            disparos.append({
                "ticker": a["ticker"],
                "close": close,
                "direcao": a["direcao"],
                "limite": a["limite"],
                "data": q["trade_date"],
            })
    return disparos

for d in checar_alertas():
    print(f"{d['ticker']} fechou {d['direcao']} de R$ {d['limite']:.2f}: "
          f"R$ {d['close']:.2f} em {d['data']}")
PETR4 fechou abaixo de R$ 30.00: R$ 29.71 em 2026-07-01 ITUB4 fechou acima de R$ 38.00: R$ 38.45 em 2026-07-01

Os números do output são ilustrativos apenas para mostrar a lógica: dois gatilhos dispararam e um (VALE3) não. Nesse ponto o script já é útil rodado manualmente. Falta enviar as mensagens para fora do terminal e fazer o processo rodar sozinho.

Ainda não tem uma chave? Crie a conta gratuita no dashboard, gere a API key e o plano gratuito de 200 requisições por dia já cobre um monitor de carteira pessoal.

Criar chave gratuita

Enviando a notificação: e-mail e Telegram

A etapa de notificação é independente da bolsai. Qualquer canal serve; os dois mais práticos são e-mail, que não exige nenhuma dependência extra, e Telegram, que entrega no celular quase instantaneamente. A bolsai continua responsável apenas por fornecer a cotação; o envio usa serviços genéricos.

Notificação por e-mail com smtplib

A biblioteca smtplib faz parte da biblioteca padrão do Python e envia por qualquer servidor SMTP. Guarde usuário e senha em variáveis de ambiente, nunca no código. Muitos provedores exigem uma senha de aplicativo específica em vez da senha normal da conta.

import os
import smtplib
from email.message import EmailMessage

def enviar_email(assunto, corpo):
    msg = EmailMessage()
    msg["Subject"] = assunto
    msg["From"] = os.environ["SMTP_USER"]
    msg["To"] = os.environ["ALERTA_DEST"]
    msg.set_content(corpo)

    with smtplib.SMTP_SSL(os.environ["SMTP_HOST"], 465) as s:
        s.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
        s.send_message(msg)

Notificação por Telegram com a Bot API

Para o Telegram, abra uma conversa com o BotFather, crie um bot com o comando /newbot e guarde o token. Descubra o seu chat_id enviando uma mensagem ao bot e consultando o endpoint getUpdates, ou usando um bot utilitário de id. O envio é uma única requisição HTTP para o método sendMessage:

import os
import requests

def enviar_telegram(texto):
    token = os.environ["TELEGRAM_TOKEN"]
    chat_id = os.environ["TELEGRAM_CHAT_ID"]
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    r = requests.post(url, json={"chat_id": chat_id, "text": texto}, timeout=10)
    r.raise_for_status()

Com um canal escolhido, basta formatar os disparos em uma mensagem legível e enviar. Um único envio por rodada, agrupando todos os alertas do dia, é melhor do que uma mensagem por papel:

def notificar(disparos):
    if not disparos:
        return
    linhas = [
        f"{d['ticker']}: R$ {d['close']:.2f} (gatilho {d['direcao']} de "
        f"R$ {d['limite']:.2f}) no fechamento de {d['data']}"
        for d in disparos
    ]
    texto = "Alertas de fechamento B3\n\n" + "\n".join(linhas)
    enviar_telegram(texto)   # ou enviar_email("Alertas B3", texto)

notificar(checar_alertas())
Alertas de fechamento B3 PETR4: R$ 29.71 (gatilho abaixo de R$ 30.00) no fechamento de 2026-07-01 ITUB4: R$ 38.45 (gatilho acima de R$ 38.00) no fechamento de 2026-07-01

Agendando o script para rodar sozinho

Um alerta só é útil se roda sem intervenção. A regra de ouro é agendar a execução para depois das 20h30 de Brasília, quando a cotação de fechamento do dia já está disponível. Rodar antes disso lê o fechamento do dia anterior, e o alerta perde o sentido. Um horário seguro é 21h ou 21h30, dando margem para o processamento diário concluir.

Opção 1: cron em Linux ou macOS

Em uma máquina que fica ligada, ou em um servidor barato, o cron resolve. A linha abaixo roda o script às 21h em dias úteis (segunda a sexta). Ajuste o fuso da máquina para o horário de Brasília ou converta o horário na expressão:

# crontab -e
# min hora dia mês dia-semana   comando
0 21 * * 1-5  cd /home/user/alertas && /usr/bin/python3 alertas.py >> alertas.log 2>&1

O redirecionamento para alertas.log guarda a saída e eventuais erros, o que facilita descobrir por que uma rodada falhou. As variáveis de ambiente com a API key e as credenciais de notificação precisam estar disponíveis para o processo do cron; um caminho comum é carregá-las de um arquivo .env no início do script.

Opção 2: GitHub Actions, sem servidor

Quem não quer manter uma máquina ligada pode agendar o script em um repositório com GitHub Actions. O gatilho schedule usa sintaxe cron em UTC; 21h de Brasília equivale a 00h UTC (com o Brasil em UTC−3). Guarde a API key e os tokens como secrets do repositório, nunca no código.

# .github/workflows/alertas.yml
name: alertas-b3
on:
  schedule:
    - cron: "0 0 * * 2-6"   # 00h UTC = 21h BRT, ter-sáb cobre pregões seg-sex
  workflow_dispatch: {}
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install requests
      - run: python alertas.py
        env:
          BOLSAI_API_KEY: ${{ secrets.BOLSAI_API_KEY }}
          TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}

O agendador do GitHub Actions é best-effort: em horários de pico a execução pode atrasar alguns minutos. Para alertas de fechamento isso é irrelevante, já que a janela útil é de horas. Se um dia específico exigir precisão de minuto, o agendador gratuito não é a ferramenta certa.

Evitando alertas repetidos com controle de estado

Sem memória, o script reavisa o mesmo gatilho a cada rodada enquanto a condição continuar verdadeira. A correção é registrar quais gatilhos já dispararam e só notificar na transição de falso para verdadeiro. Um arquivo JSON simples guarda o estado entre execuções. A chave combina ticker, direção e limite, de forma que mudar o preço-alvo rearma o alerta:

import json
import os

ESTADO = "estado_alertas.json"

def carregar_estado():
    if os.path.exists(ESTADO):
        with open(ESTADO) as f:
            return set(json.load(f))
    return set()

def salvar_estado(chaves):
    with open(ESTADO, "w") as f:
        json.dump(sorted(chaves), f)

def chave(a):
    return f"{a['ticker']}|{a['direcao']}|{a['limite']}"

def rodar():
    ja_avisados = carregar_estado()
    ativos, novos = set(), []
    for a in ALERTAS:
        q = cotacao_fechamento(a["ticker"])
        if disparou(a["direcao"], q["close"], a["limite"]):
            k = chave(a)
            ativos.add(k)
            if k not in ja_avisados:   # só notifica na transição
                novos.append({**a, "close": q["close"], "data": q["trade_date"]})
    if novos:
        notificar(novos)
    salvar_estado(ativos)   # quem saiu da zona é rearmado

rodar()

Agora o script avisa uma vez quando o gatilho é atingido e silencia enquanto a condição persiste. Se o preço sair da zona e voltar a entrar em outro dia, o alerta dispara de novo, que é o comportamento desejado. Para monitorar a carteira de forma conversacional em vez de por gatilhos fixos, o tutorial de agente de IA para monitorar carteira mostra uma abordagem complementar.

Vários papéis, rate limit e boas práticas

Cada checagem consome uma requisição por ticker. Monitorar dez papéis uma vez ao dia gasta dez das 200 requisições diárias do plano gratuito. Mesmo rodando duas vezes ao dia, o consumo fica muito abaixo do teto. O plano gratuito é dimensionado para exatamente esse caso; a consulta de cotação em Python cobre os detalhes de tratamento de erro e cache que valem para qualquer script sobre o endpoint de cotação. Três cuidados evitam a maioria dos problemas de produção:

A migração para o plano Pro, com 10.000 requisições por dia e histórico completo, só se justifica quando o alerta deixa de ser pessoal e vira um serviço multiusuário, ou quando passa a varrer todo o universo da B3 em vez de uma lista curta.

Alertas por variação e por fundamento com o screener

Além do gatilho de preço absoluto, dá para alertar sobre variação diária ou sobre condições fundamentalistas combinadas. O endpoint /screener aceita filtros com sufixos _gt (maior que) e _lt (menor que) sobre indicadores, e devolve uma lista já filtrada em uma única requisição. Ele exige o plano Pro. Um uso típico é "avisar quais papéis fecharam em queda forte no dia", usando o campo daily_change_pct:

import requests

BASE = "https://api.usebolsai.com/api/v1"
HEADERS = {"X-API-Key": "sk_sua_chave_aqui"}

# Papéis que caíram mais de 5% no fechamento do dia, ordenados pela queda.
params = {
    "daily_change_pct_lt": -5,
    "sort": "daily_change_pct",
    "order": "asc",
    "limit": 20,
}
r = requests.get(f"{BASE}/screener", headers=HEADERS, params=params, timeout=15)
r.raise_for_status()
dados = r.json()["data"]

for item in dados:
    print(f"{item['ticker']:6} {item['daily_change_pct']:>6.2f}%  R$ {item['close_price']:.2f}")
TICKER VAR% PREÇO XXXX3 -7.80% R$ 12.34 YYYY4 -6.15% R$ 41.02 ZZZZ11 -5.44% R$ 8.97

A resposta do screener vem no formato {"data": [...], "count": ..., "total": ...}, e cada item traz dezenas de indicadores. O exemplo acima usa tickers e números claramente hipotéticos só para ilustrar o formato; a lista real depende do fechamento do dia em que você rodar. A mesma lógica de gatilho e notificação dos blocos anteriores se aplica: filtre no screener, monte a mensagem, envie pelo canal escolhido. Vale lembrar que daily_change_pct aqui é a variação de um fechamento para o outro, não uma variação intradiária.

Perguntas frequentes

Os alertas são em tempo real?

Não. A bolsai entrega a cotação de fechamento do último pregão e atualiza o registro uma vez por dia útil, logo após o encerramento da sessão regular da B3, por volta das 20h30 de Brasília. Um script montado sobre esse dado gera alertas de fim de pregão, não alertas intradiários. Ele avisa quando o fechamento do dia cruzou um gatilho, o que serve para swing trade e position trade. Para disparos no mesmo minuto em que o preço se move durante o pregão, é preciso um feed de cotação ao vivo, que é um produto diferente.

Para quem serve um alerta de preço de fechamento?

Para quem opera em horizonte de dias, semanas ou meses e revisa a carteira uma vez ao dia. Um investidor de dividendos que quer ser avisado quando um papel cai abaixo do preço-teto, um swing trader que acompanha rompimentos de suporte e resistência no gráfico diário, ou alguém montando posição gradual que quer saber quando o ativo atinge a faixa de compra. Day traders e scalpers precisam de dados intradiários e não devem usar cotação de fechamento como base de alerta.

Quantas requisições um alerta diário consome?

Uma requisição por ticker por execução. Monitorar dez papéis uma vez por dia gasta dez das 200 requisições diárias do plano gratuito, deixando ampla folga. Mesmo checando duas vezes ao dia, o consumo fica muito abaixo do limite. O plano gratuito cobre com sobra o caso de uma carteira pessoal; a migração para o plano Pro (10.000 requisições por dia) só faz sentido quando o script vira um serviço multiusuário ou passa a varrer todo o universo da B3.

Como envio o alerta por Telegram ou e-mail?

O envio é independente da bolsai. Para Telegram, crie um bot pelo BotFather, guarde o token e chame a API https://api.telegram.org/bot<TOKEN>/sendMessage com o seu chat_id. Para e-mail, a biblioteca smtplib da própria stdlib do Python envia por qualquer servidor SMTP. A bolsai entra apenas na etapa de obter a cotação; a notificação usa serviços genéricos que você já tem.

Como faço o script rodar sozinho todo dia?

Agende a execução para depois das 20h30 de Brasília, quando a cotação de fechamento já está disponível. Em Linux ou macOS, um cron job resolve. Em um repositório GitHub, um workflow do GitHub Actions com gatilho schedule roda sem servidor próprio. O importante é agendar após o horário de atualização dos dados; rodar antes disso lê o fechamento do dia anterior.

Comece agora

Criar alertas de preço de ações python deixou de exigir scraping frágil ou serviço pago de monitoramento. Uma chamada autenticada a /stocks/{ticker}/quote devolve o fechamento, um punhado de linhas avalia os gatilhos, e smtplib ou a Bot API do Telegram entrega a mensagem, tudo agendado por cron ou GitHub Actions após as 20h30. O recorte honesto é o que dá valor ao projeto: por serem alertas de fechamento, eles servem swing trade, position trade e montagem gradual de posição, e não day trade. O plano gratuito de 200 requisições por dia cobre uma carteira pessoal inteira, então dá para validar a ideia sem custo antes de qualquer upgrade.

Leia também

Este conteúdo é educativo e não constitui recomendação de investimento. Os valores de cotação usados nos exemplos são ilustrativos e não representam preços reais. Performance passada não garante resultados futuros.