Sistema de monitoramento e alerta (não executa ordens) para uma watchlist de ações da NASDAQ. Acompanha preço/volume, calcula indicadores técnicos (SMA, EMA, RSI, MACD, Bollinger, volume médio) e dispara alertas configuráveis via Telegram e num dashboard web.
⚠️ Ferramenta de apoio à decisão. Não é recomendação de investimento, não executa ordens de compra/venda e usa dados de fontes gratuitas que podem ter atraso. Sempre valide antes de operar de verdade (ex: na Exness ou outra corretora).
Inclui também um painel de mercado com notícias por ativo, calendário econômico e calendário de earnings, regras de alerta compostas (E/OU) com backtest antes de salvar, posições/P&L manuais, e um assistente com IA que explica os dados coletados (nunca recomenda comprar/vender).
Backend e front-end são dois serviços separados:
- Backend (
app/): FastAPI, API REST pura (JSON), autenticação por JWT (Bearer token, sem cookie de sessão), SQLite + APScheduler + bot do Telegram. - Front-end (
frontend/): React + TypeScript + Vite, um SPA que consome o backend viafetch. Guarda o token JWT nolocalStoragee manda emAuthorization: Bearer <token>em cada request.
Por quê separado: permite hospedar cada parte de forma independente (ex: backend num Web
Service e front num Static Site no Render), o que é o modelo "cloud-native" padrão. O preço
disso é precisar de CORS (o backend só aceita requests da origem configurada em
FRONTEND_ORIGIN) e token em vez de cookie (cookies cross-domain entre dois serviços do
Render dariam mais dor de cabeça que um Bearer token simples).
- Backend: FastAPI + SQLAlchemy (SQLite) + APScheduler + PyJWT
- Front-end: React 19 + TypeScript + Vite + React Router + Chart.js (candlestick)
- Dados: Finnhub (cotação, notícias e earnings, free tier) +
yfinance(histórico para indicadores, sem necessidade de API key) + Financial Modeling Prep (calendário econômico, free tier) - Alertas: bot do Telegram (
python-telegram-bot) - Assistente IA: Anthropic Claude, Google Gemini ou Groq (à sua escolha, ver seção própria)
O projeto mantém o yfinance como fonte permanente para histórico OHLCV usado em gráficos,
indicadores, backtests, volatilidade, Mesa Técnica, Resumo Diário e simulações. Finnhub continua
útil para cotação/notícias/earnings, mas a análise técnica depende de candles históricos; por isso
o endpoint operacional marca yfinance_required_for_technical_analysis=true e testa
yfinance_available em /api/operations/health.
O Investing.com não oferece API pública. O único jeito de puxar dados de lá programaticamente
é via scraping não-oficial (bibliotecas como investiny), o que viola os termos de uso do site
e quebra sem aviso quando eles mudam o HTML — não é uma base confiável para algo que o pai do
seu amigo vai usar de verdade. Por isso, cotações/notícias/calendário econômico vêm de APIs
oficiais (Finnhub + FMP), que cobrem a mesma necessidade com estabilidade e dentro do free tier.
Para rodar os processos separados em desenvolvimento:
copy .env.example .env
docker compose up --buildServiços:
api: FastAPI emhttp://localhost:8000, sem scheduler embutido.worker: scheduler, coleta de dados, bots e resumos Telegram.frontend: Vite emhttp://localhost:5173.
Para um frontend estático servido por Nginx:
docker compose -f docker-compose.prod.yml up --buildNesse modelo o worker é o único processo que roda automações; isso evita duplicar alertas,
coletas e mensagens de bot quando a API escala.
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements.txt
copy .env.example .envEdite o .env:
- Finnhub: crie uma conta grátis em https://finnhub.io/register e copie a API key para
FINNHUB_API_KEY(usada para cotação, notícias e calendário de earnings). - Financial Modeling Prep: crie uma conta grátis em
https://financialmodelingprep.com/developer/docs/ e copie a API key para
FMP_API_KEY(usada só para o calendário econômico — juros, payroll, inflação etc.). Se não configurar, o sistema roda normalmente, só sem o calendário econômico. - Telegram:
- Fale com @BotFather no Telegram, crie um bot (
/newbot) e copie o token paraTELEGRAM_BOT_TOKEN. - Envie qualquer mensagem (ex:
/start) para o seu bot recém-criado. - Rode
python get_chat_id.pypara descobrir oTELEGRAM_CHAT_ID— cole no.env. Isso garante que só esse chat (o do pai do seu amigo) pode usar o bot.
- Fale com @BotFather no Telegram, crie um bot (
- Login da API: gere uma
SECRET_KEYcompython -c "import secrets; print(secrets.token_hex(32))"e cole no.env. Sem isso o sistema ainda funciona (gera uma chave temporária e avisa no log), mas todo mundo perde a sessão a cada restart do servidor — não use isso em produção. - Assistente com IA (opcional): ver seção "Assistente com IA" abaixo.
Rodar localmente:
uvicorn app.main:app --reloadA API sobe em http://localhost:8000 (endpoints em /api/..., /health pra checar se subiu).
cd frontend
npm install
copy .env.example .env
npm run devAcesse http://localhost:5173 — a primeira visita redireciona pra /cadastro, onde qualquer
usuário pode criar conta e entrar em seguida (ver seção "Autenticação" abaixo).
Atenção:
FRONTEND_ORIGINno.envdo backend precisa bater exatamente com a URL que você acessa o front no navegador (http://localhost:5173, e não127.0.0.1:5173— são origens diferentes pro CORS, mesmo apontando pra mesma máquina).
Login por token (JWT):
- Cadastro aberto:
POST /api/auth/cadastrocria a conta e já devolve um token para entrar. A primeira conta criada vira administradora automaticamente; as seguintes entram como usuários comuns. - Admins ainda podem criar contas manualmente e escolher permissão de admin na tela
/usuarios. - Senhas ficam com hash bcrypt (nunca em texto plano). O login devolve um token JWT que o front
guarda no
localStoragee manda emAuthorization: Bearer <token>em cada request — sem cookie, sem CSRF (não tem cookie automático do navegador pra explorar). - Token expira em
JWT_EXPIRE_HOURS(padrão 7 dias) — depois disso precisa logar de novo. Sem refresh token (mantido simples de propósito, ver "Limitações conhecidas"). - Rate limit simples no login: 5 tentativas erradas seguidas bloqueiam por 5 minutos (contador em memória, reseta se o processo reiniciar — trava contra brute force casual, não solução enterprise).
- Sem "esqueci minha senha" — se alguém esquecer, um admin recria o usuário direto no banco (ou me chama que eu ajudo). Não há serviço de e-mail configurado no projeto.
Fluxo sugerido: cada pessoa pode fazer o próprio cadastro e entrar em seguida. Se alguém precisar
virar admin, uma conta administradora pode ajustar isso em /usuarios.
Backend:
pytestValidacao do simulador com dados reais do Yahoo Finance:
python scripts/run_simulation_validation.pyNo Docker:
docker compose up --build -d
docker compose exec api python scripts/run_simulation_validation.py
docker compose logs -f paper-simulatorFront-end:
cd frontend
npm run testBackend cobre indicadores técnicos, motor de regras (E/OU), backtest, posições/P&L, dedup,
auth (fluxo JWT completo) e as rotas de API — tudo sem depender de API externa real (LLM é
mockado nos testes). Front-end cobre AuthContext (login/logout/persistência de token) e o
hook useRuleConditions (montagem do payload de condições da regra composta) — cobertura
pontual, não e2e completo de cada página (validado manualmente, ver "Smoke test" abaixo).
- Acesse
/watchlistno front (ou use/add SYMBOLno bot do Telegram) para cadastrar ativos, ex:AAPL,MSFT,NVDA. - Para cada ativo, crie regras de alerta na própria tela de watchlist: adicione uma ou mais condições (preço acima/abaixo, RSI, cruzamento de médias, MACD, spike de volume, variação %), escolha se é "TODAS" (E) ou "QUALQUER" (OU) entre elas, e clique em "Testar regra" pra ver o backtest antes de salvar.
- Registre suas compras/vendas em
/posicoespra acompanhar custo médio e P&L — é só um registro manual, não afeta nem depende de nenhuma corretora. - Use
/assistenteno front (ou/pergunta <texto>no Telegram) pra perguntar coisas como "por que a AAPL caiu hoje?" — a resposta usa só os dados que o sistema já coletou. - O scheduler interno do backend:
- a cada
QUOTE_POLL_SECONDS(padrão 60s) busca a cotação atual de cada ativo ativo; - a cada
INDICATOR_REFRESH_SECONDS(padrão 5min) recalcula indicadores e avalia as regras; - a cada
NEWS_REFRESH_SECONDS(padrão 30min) busca notícias novas de cada ativo; - todo dia às
CALENDAR_REFRESH_HOUR_UTCatualiza calendário econômico e de earnings; - todo dia às
DAILY_SUMMARY_HOUR_UTCenvia um resumo pelo Telegram (preços + notícias das últimas 24h + eventos econômicos de alto impacto do dia + earnings da semana).
- a cada
- Quando uma regra dispara: grava no histórico de alertas, aparece no front e é enviado via
Telegram (respeitando o
cooldown_minutesde cada regra, pra não spammar). - Acesse
/mercadopara ver o painel completo de notícias, calendário econômico e earnings. - Baixe um relatório em PDF a qualquer momento pelo botão "Baixar PDF" no front, ou mande
/relatoriopara o bot no Telegram — ele gera e envia o PDF na hora, com watchlist, alertas recentes, notícias, calendário econômico e earnings.
O Docker sobe tambem o servico paper-simulator, que roda uma carteira ficticia de US$200 em
segundo plano. Ele nao envia ordem real. A cada ciclo ele:
- busca historico OHLCV pelo Yahoo Finance via
yfinance; - recalibra filtros tecnicos com RSI, medias, MACD, volume, ATR e volatilidade;
- mede falsos positivos olhando o retorno dos 5 pregoes seguintes em sinais historicos;
- so permite compra se a precisao historica for pelo menos 58% e o retorno medio for positivo;
- compra apenas acoes inteiras que caibam no caixa, sem alavancagem;
- gerencia posicoes com stop de 1 ATR, alvo de 2 ATR e saida por virada de tendencia.
Arquivos para acompanhar:
data/paper_simulator_events.jsonl: calibracoes, decisoes, compras, vendas, stops e motivos de espera.data/paper_simulator_state.json: caixa, posicoes abertas e trades fechados.data/simulation_validation_report.json: relatorio gerado pelo comando de validacao.
Uma resposta NO_TRADE pode ser a decisao correta. Se o filtro historico ficar abaixo do minimo
de confianca, o simulador preserva o caixa em vez de forcar uma entrada.
Usa um LLM só pra explicar dados que o sistema já coletou, nunca pra decidir ou executar
nada. Três provedores suportados via LLM_PROVIDER no .env do backend:
groq— grátis, sem cartão de crédito, modelos Llama bem rápidos. Crie a key em https://console.groq.com/keys e cole emGROQ_API_KEY.gemini— grátis, sem cartão de crédito. Crie a key em https://aistudio.google.com/apikey e cole emGEMINI_API_KEY. Atenção: contas novas do Google às vezes vêm com o projeto associado à key suspenso (CONSUMER_SUSPENDED) até passar por verificação adicional — se isso acontecer, usegroqouanthropicem vez disso.anthropic— pago (créditos pré-pagos), recomendado quando for pra produção de verdade. Crie a key em https://console.anthropic.com e cole emANTHROPIC_API_KEY.
O código dos três é idêntico (mesmos prompts, mesmo comportamento) — só troca o LLM_PROVIDER
quando quiser migrar de um pro outro. Sem nenhuma key configurada, o sistema roda normal: o
resumo diário fica em formato de lista simples e o assistente avisa que está desativado.
- Resumo diário narrativo: o job
daily_summarymonta os mesmos dados de sempre (preços, notícias, eventos econômicos, earnings) e pede pro LLM escrever um parágrafo curto em vez de só listar números. Se a API falhar, cai automaticamente pro formato de lista simples — nunca quebra o envio do resumo. - Chat (
/assistenteno front,/pergunta <texto>no Telegram): responde só com base na watchlist/notícias/alertas que já estão no banco. Instruído a dizer "não sei" em vez de inventar quando a informação não está disponível, e a nunca recomendar comprar/vender. - Contexto nos alertas (
LLM_ENRICH_ALERTS=true, desligado por padrão): adiciona uma frase de contexto em cada alerta disparado. Fica desligado por padrão porque alertas podem disparar com frequência e cada um vira uma chamada de API — ligue só se souber o volume de alertas que sua watchlist costuma gerar.
Custo esperado (Groq/Gemini grátis, ou Anthropic Haiku): com uso de baixo volume (1 resumo/dia
- perguntas ocasionais), fica na faixa de centavos de dólar por mês (ou zero, nos provedores grátis).
Dois serviços no Render (não pede cartão de crédito no free tier):
- Crie uma conta grátis em https://render.com (pode entrar com GitHub).
- Suba este repositório para o GitHub (crie um repo privado —
.envefrontend/.envnão vão junto, estão no.gitignore). - No painel do Render: New + → Web Service → conecte o repositório.
- Environment: Docker (ele detecta o
Dockerfilena raiz automaticamente). - Em Environment Variables, cole todas as chaves do seu
.envlocal (FINNHUB_API_KEY,FMP_API_KEY,TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID,SECRET_KEY,JWT_EXPIRE_HOURS, provedor de LLM escolhido, etc.) — uma por uma no painel, nunca commitando o arquivo.SECRET_KEYprecisa ser fixa aqui (gerada uma vez, colada no painel) — se ficar em branco, cada restart gera uma nova e invalida todos os tokens JWT emitidos. - Depois de criar o front-end (passo 2 abaixo), volte aqui e configure
FRONTEND_ORIGINcom a URL do site estático do Render (ex:https://monitor-nasdaq.onrender.com). - Plano Free: o serviço "dorme" após ~15 min sem requisições HTTP, e o scheduler interno (jobs do APScheduler) só roda enquanto o processo está de pé — no free tier o monitoramento não é 100% contínuo. Para monitoramento 24/7 de verdade, migre pro plano Starter (~US$7/mês), que não dorme.
- Persistência: o SQLite fica no filesystem do container, que é efêmero no Render — se o
serviço reiniciar, o histórico de preços/alertas E as contas de usuário/senha zeram (o
cadastro reabre sozinho, o que é seguro mas incômodo). Pra persistir de verdade, adicione um
Render Disk (storage persistente, custo baixo) apontando pro caminho do
DATABASE_URL, ou migre para o Render Postgres (free tier disponível) trocandoDATABASE_URL— o SQLAlchemy já suporta ambos sem mudar código. Recomendo fortemente configurar isso antes de considerar o deploy "definitivo".
- No painel do Render: New + → Static Site → conecte o mesmo repositório.
- Root Directory:
frontend. - Build Command:
npm install && npm run build. - Publish Directory:
dist. - Em Environment Variables, adicione
VITE_API_URLcom a URL do backend (passo 1 acima, ex:https://monitor-nasdaq-api.onrender.com). Importante: essa variável fica embutida no build (Vite lê em build time) — se você mudar depois, precisa disparar um novo deploy pra valer. - Depois do primeiro deploy, copie a URL gerada e cole em
FRONTEND_ORIGINno serviço do backend (passo 6 da seção anterior), senão o CORS bloqueia tudo.
O front-end (frontend/) é uma SPA Vite/React pura — encaixa bem no Vercel. O backend
(FastAPI + APScheduler + SQLite + bot do Telegram) não roda no Vercel (é um processo
persistente com jobs em background, incompatível com funções serverless); ele continua num
serviço à parte (Render, Fly.io, VPS — ver seção anterior).
- Crie uma conta grátis em https://vercel.com (pode entrar com GitHub) e suba este
repositório para o GitHub primeiro (repo privado —
.envnão vai junto, já está no.gitignore). - No painel do Vercel: Add New → Project → importe o repositório.
- Root Directory:
frontend(existe umfrontend/vercel.jsonjá configurado com o rewrite de SPA — sem ele, atualizar a página em qualquer rota tipo/mesa-iadá 404). O Vercel detecta o preset Vite automaticamente (npm run build, saída emdist). - Em Environment Variables, adicione
VITE_API_URLapontando pro backend (ex:https://monitor-nasdaq-api.onrender.com). Importante: essa variável é lida em build time pelo Vite — mudar depois exige um novo deploy (Vercel → Deployments → Redeploy). - Depois do primeiro deploy, copie a URL gerada (ex:
https://seu-projeto.vercel.app) e cole emFRONTEND_ORIGINno serviço do backend, senão o CORS bloqueia as chamadas da API. - Deploys automáticos: todo push na branch principal do GitHub gera um novo deploy sozinho.
- Railway: mesmo fluxo pros dois serviços, mas hoje em dia costuma pedir cartão pra liberar o free tier — por isso ficamos com o Render como recomendação principal.
- Fly.io (backend) + Render Static Site / Vercel / Netlify (front):
fly launch # gera fly.toml, escolha "no" para banco gerenciado (usamos SQLite local) fly secrets set FINNHUB_API_KEY=... FMP_API_KEY=... TELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=... SECRET_KEY=... FRONTEND_ORIGIN=... fly deploy
- VPS próprio (Oracle Cloud free tier, etc.) pro backend:
Sobe o backend na porta 8000 com restart automático. Configure um proxy reverso (Caddy/Nginx) com HTTPS na frente se for expor publicamente. O front-end (
docker compose up -d --build
frontend/dist, gerado pornpm run build) pode ser servido por qualquer host estático (Nginx, Vercel, Netlify, Render Static Site).
app/ # backend (API pura)
main.py # FastAPI app + CORS + lifecycle (DB, bot, scheduler)
config.py # variáveis de ambiente
db.py, models.py, schemas.py
auth.py # hash de senha, JWT, rate limit de login
indicators.py # SMA, EMA, RSI, MACD, Bollinger, volume ratio
rules_engine.py # avalia condições/regras (lógica E/OU) contra dados de mercado
backtest.py # roda uma regra contra o histórico antes de salvar
positions.py # custo médio, P&L realizado/não-realizado a partir de transações
llm_client.py # wrapper async multi-provider (Anthropic/Gemini/Groq)
dedup.py # dedup pura de notícias/eventos (testável sem DB/rede)
scheduler.py # jobs periódicos (cotação, regras, notícias, calendários, resumo)
telegram_bot.py # comandos do bot + envio de alertas
market_data/ # clientes Finnhub, yfinance e FMP
reports.py # gera o relatório PDF (reportlab)
routers/ # auth, watchlist, positions, assistant, reports, api
tests/ # testes do backend (indicadores, regras, backtest,
# posições, dedup, API, auth JWT, assistente)
frontend/ # front-end (SPA)
src/
api/client.ts # fetch wrapper com Authorization: Bearer, trata 401 global
context/ # AuthContext (JWT), ToastContext
components/ # Navbar, ProtectedRoute, ConfirmModal, CandlestickChart,
# RuleConditionBuilder
hooks/ # usePolling, useRuleConditions
pages/ # Login, Cadastro, Dashboard, Watchlist, Mercado, Alertas,
# Posicoes, Assistente, Usuarios, AtivoDetalhe
styles/global.css # design system (dark theme, tabelas, forms, toasts, chat)
- Dados podem ter alguns minutos de atraso dependendo do plano do Finnhub/yfinance/FMP.
- Finnhub free tier: 60 requisições/minuto — suficiente para uma watchlist pequena/média.
- yfinance é uma biblioteca não-oficial que consome dados públicos do Yahoo Finance; pode falhar ocasionalmente se o Yahoo mudar algo — o código já trata erros sem derrubar o serviço.
- Calendário econômico depende da FMP; sem
FMP_API_KEYconfigurada essa seção fica vazia mas o resto do sistema continua funcionando normalmente. - Assistente/resumo narrativo dependem de uma API key de LLM configurada; sem ela, tudo cai pro comportamento sem IA (sem quebrar nada).
- Backtest é simplificado (não é um motor de backtesting completo): reavalia a regra em janela deslizante sobre o histórico do yfinance, não simula slippage/custos/execução real.
- Sem refresh token: expirado o
JWT_EXPIRE_HOURS, precisa logar de novo — trade-off deliberado pra manter a auth simples num app de poucos usuários. - Sem execução de ordens: qualquer decisão de compra/venda continua manual, feita por você na corretora (ex: Exness).