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:
- Consultar a cotação de fechamento de cada ticker monitorado.
- Avaliar cada gatilho contra o preço retornado.
- Notificar apenas os gatilhos que dispararam.
- 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']}")
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']}")
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 gratuitaEnviando 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())
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:
- Trate o 429. Se um dia o script crescer e ultrapassar o limite, a API responde
429; respeite o header
Retry-Aftere tente de novo depois da espera. - Guarde segredos fora do código. API key, token do bot e senha SMTP vão em variáveis de ambiente ou secrets, jamais versionados.
- Confira o
trade_date. É a defesa contra disparar alertas sobre um fechamento defasado quando o script roda cedo demais ou em dia sem pregã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}")
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.