Dashboard de Apostas Esportivas com React
Tutorial completo para construir um dashboard que mostra odds em tempo real, detecta arbitragens e exibe tendências de mercado. React + Chart.js + APIs de odds.
Por que um dashboard muda tudo
Dashboard de Apostas Esportivas com React. Tutorial completo para construir um dashboard que mostra odds em tempo real, detecta arbitragens e exibe tendências de mercado. React + Chart.js + APIs de odds.
Dashboard visual muda o jogo
Galera, vou contar uma coisa que aprendi na prática: dados em tabela são ótimos pra armazenar informação, mas péssimos pra tomar decisão rápida. Quando uma odd de arbitragem aparece, ela dura minutos — às vezes segundos. Você não vai ficar scrollando uma planilha com 500 linhas tentando achar o número certo enquanto a oportunidade desaparece.
Um dashboard bem feito mostra pra você em 2 segundos o que importa. Gráfico de linha mostrando pra onde a odd está indo. Cards coloridos destacando arbitragens ativas. Tabela filtrada com só as oportunidades relevantes. Tudo atualizado em tempo real.
A stack que vamos usar aqui é React com TypeScript no front, Chart.js pra gráficos (via react-chartjs-2), e The Odds API como fonte de dados. Dá pra deployar como PWA, instalar no celular e usar como um app nativo. Custo de infraestrutura: zero se você usar Vercel ou Netlify no plano gratuito.
O projeto final tem três telas: overview com cards de métricas e gráfico de odds, tabela de arbitragens em tempo real, e tela de detalhes por jogo com histórico de movimento. Vamos construir tudo isso passo a passo.
Arquitetura React + Chart.js
Antes de sair codando, vale pensar na arquitetura. A estrutura de pastas que funciona bem pra esse tipo de projeto separa as preocupações em camadas: hooks pra lógica de dados, components pra UI, e services pra comunicação com APIs.
- Crie o projeto com Vite: npm create vite@latest betting-dashboard -- --template react-ts
- Instale as dependências: npm install chart.js react-chartjs-2 axios tailwindcss @headlessui/react
- Configure Tailwind CSS no projeto seguindo a documentação oficial
- Crie a estrutura de pastas: src/hooks, src/components, src/services, src/types
- Defina os tipos TypeScript para odds, games e arbitragens em src/types/betting.ts
- Implemente o service de API em src/services/oddsApi.ts
- Crie os hooks customizados: useOdds, useArbitrages, useOddsHistory
Estrutura de componentes
O componente raiz é o Dashboard, que orquestra três painéis: StatsOverview (cards com métricas), OddsChart (gráfico de linha) e ArbitrageTable (tabela interativa). Cada componente recebe dados via props e não faz fetch sozinho — toda a lógica de dados fica nos hooks.
// src/types/betting.ts
export interface OddsData {
gameId: string;
homeTeam: string;
awayTeam: string;
commenceTime: string;
bookmaker: string;
oddHome: number;
oddAway: number;
oddDraw: number;
collectedAt: string;
}
export interface Arbitrage {
gameId: string;
homeTeam: string;
awayTeam: string;
book1: string;
book2: string;
book3?: string;
oddHome: number;
oddAway: number;
oddDraw: number;
profit: number; // percentual de lucro garantido
}
export interface StatsCard {
label: string;
value: string | number;
change?: number; // variação percentual
trend: 'up' | 'down' | 'neutral';
}A tipagem forte com TypeScript aqui não é frescura. Quando você está lidando com dados financeiros e decisões que envolvem dinheiro, um campo undefined que passa batido pode custar caro. TypeScript pega isso no build.
Gráfico de odds em tempo real
O gráfico principal do dashboard mostra a evolução das odds de um jogo ao longo do tempo. Cada casa de apostas é uma linha no gráfico. Quando as linhas divergem muito, pode ter arbitragem. Quando convergem, o mercado está em consenso. Essa visualização sozinha já vale mais que qualquer planilha.
Hook de dados para o gráfico
// src/hooks/useOddsHistory.ts
import { useState, useEffect, useCallback } from 'react';
import { OddsData } from '../types/betting';
import { fetchOdds } from '../services/oddsApi';
export function useOddsHistory(gameId: string, intervalMs = 300000) {
const [history, setHistory] = useState<OddsData[]>([]);
const [loading, setLoading] = useState(true);
const poll = useCallback(async () => {
try {
const data = await fetchOdds(gameId);
setHistory(prev => [...prev, ...data]);
} catch (err) {
console.error('Erro ao buscar odds:', err);
} finally {
setLoading(false);
}
}, [gameId]);
useEffect(() => {
poll();
const timer = setInterval(poll, intervalMs);
return () => clearInterval(timer);
}, [poll, intervalMs]);
const chartData = {
labels: [...new Set(history.map(h =>
new Date(h.collectedAt).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
))],
datasets: Object.entries(
history.reduce((acc, h) => {
if (!acc[h.bookmaker]) acc[h.bookmaker] = [];
acc[h.bookmaker].push(h.oddHome);
return acc;
}, {} as Record<string, number[]>)
).map(([bookmaker, odds], i) => ({
label: bookmaker,
data: odds,
borderColor: ['#22c55e', '#3b82f6', '#f59e0b', '#ef4444', '#8b5cf6'][i % 5],
tension: 0.3,
pointRadius: 2,
})),
};
return { history, chartData, loading };
}Componente do gráfico
// src/components/OddsChart.tsx
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS, CategoryScale, LinearScale,
PointElement, LineElement, Title, Tooltip, Legend,
} from 'chart.js';
ChartJS.register(
CategoryScale, LinearScale, PointElement,
LineElement, Title, Tooltip, Legend
);
interface OddsChartProps {
chartData: {
labels: string[];
datasets: Array<{
label: string;
data: number[];
borderColor: string;
tension: number;
pointRadius: number;
}>;
};
title: string;
}
export function OddsChart({ chartData, title }: OddsChartProps) {
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' as const, labels: { color: '#a3a3a3' } },
title: { display: true, text: title, color: '#e5e5e5', font: { size: 16 } },
tooltip: {
callbacks: {
label: (ctx: any) => `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(2)}`,
},
},
},
scales: {
x: { ticks: { color: '#737373' }, grid: { color: '#262626' } },
y: {
ticks: { color: '#737373', callback: (v: any) => v.toFixed(2) },
grid: { color: '#262626' },
},
},
};
return (
<div className="bg-neutral-900 rounded-xl p-6 h-[400px]">
<Line data={chartData} options={options} />
</div>
);
}O polling de 5 minutos é um bom ponto de partida. API gratuita tem limite, então não dá pra fazer requisição a cada 10 segundos. Mas 5 minutos pega a maioria dos movimentos relevantes. Pra quem quer algo mais agressivo, WebSocket com a Betfair API é o caminho — mas aí é outro nível de complexidade.
Tabela de arbitragens em tempo real
A tabela de arbitragens é a funcionalidade mais valiosa do dashboard. Arbitragem esportiva é quando você consegue apostar em todos os resultados possíveis de um jogo, usando odds de casas diferentes, e garantir lucro independente do resultado. Acontece quando a soma dos inversos das odds de casas diferentes é menor que 1.
Lógica de detecção de arbitragem
// src/hooks/useArbitrages.ts
import { useMemo } from 'react';
import { OddsData, Arbitrage } from '../types/betting';
export function useArbitrages(oddsData: OddsData[]): Arbitrage[] {
return useMemo(() => {
const byGame = new Map<string, OddsData[]>();
oddsData.forEach(o => {
const list = byGame.get(o.gameId) || [];
list.push(o);
byGame.set(o.gameId, list);
});
const arbitrages: Arbitrage[] = [];
for (const [gameId, odds] of byGame) {
const bestHome = odds.reduce((best, o) =>
o.oddHome > best.oddHome ? o : best, odds[0]);
const bestAway = odds.reduce((best, o) =>
o.oddAway > best.oddAway ? o : best, odds[0]);
const bestDraw = odds.reduce((best, o) =>
o.oddDraw > best.oddDraw ? o : best, odds[0]);
const impliedProb = (1 / bestHome.oddHome)
+ (1 / bestAway.oddAway)
+ (1 / bestDraw.oddDraw);
if (impliedProb < 1) {
const profit = ((1 / impliedProb) - 1) * 100;
arbitrages.push({
gameId,
homeTeam: bestHome.homeTeam,
awayTeam: bestHome.awayTeam,
book1: bestHome.bookmaker,
book2: bestAway.bookmaker,
book3: bestDraw.bookmaker,
oddHome: bestHome.oddHome,
oddAway: bestAway.oddAway,
oddDraw: bestDraw.oddDraw,
profit: Math.round(profit * 100) / 100,
});
}
}
return arbitrages.sort((a, b) => b.profit - a.profit);
}, [oddsData]);
}Quando impliedProb é menor que 1, você tem arbitragem. Na prática, arbitragens acima de 1% de lucro são raras e duram pouco. Arbitragens de 0.5% aparecem mais, mas o lucro é tão pequeno que só vale se você opera com banca grande e tem as contas prontas nas casas certas. O dashboard te mostra tudo isso em tempo real pra você decidir se vale ou não.
Um detalhe que a maioria dos tutoriais ignora: casas de apostas cancelam apostas que fazem parte de arbitragem identificada. Não é ilegal, mas elas não gostam. Se você fizer arbitragem consistentemente, suas contas vão ser limitadas. Dito isso, o conhecimento de como detectar arbitragem é valioso pra entender a eficiência do mercado — mesmo que você não vá operar diretamente.
Deploy como PWA
O dashboard ganha muito quando roda no celular. Você recebe a notificação de arbitragem via Telegram (usando o bot que ensinamos no artigo sobre Telegram Bot para value bets) e abre o dashboard direto no celular pra analisar os detalhes.
Configurando PWA com Vite
Com o plugin vite-plugin-pwa, transformar sua app React em PWA leva 5 minutos. Instale: npm install vite-plugin-pwa. No vite.config.ts, adicione o plugin com as configurações de manifest — ícone, nome, cores de tema. O service worker é gerado automaticamente e faz cache dos assets estáticos.
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'Betting Dashboard',
short_name: 'BetDash',
description: 'Dashboard de odds em tempo real',
theme_color: '#0a0a0a',
background_color: '#0a0a0a',
display: 'standalone',
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
}),
],
});Deploy na Vercel é um git push. Netlify também. Ambos oferecem HTTPS gratuito, que é obrigatório pra PWA funcionar. Depois do deploy, acessa pelo celular, clica em 'Adicionar à tela inicial' e pronto — vira um app.
Checklist de deploy do dashboard
- Variáveis de ambiente configuradas (API key) no provider de deploy
- Plugin PWA configurado com ícones nos tamanhos corretos
- Service worker registrado e funcionando offline para assets estáticos
- HTTPS ativo (obrigatório para PWA)
- Responsividade testada em telas de 375px até 1440px
- Polling de odds funcionando sem vazar timers (cleanup no useEffect)
- Meta tags de SEO e Open Graph configuradas
Vercel
Deploy instantâneo com integração nativa de projetos React/Vite.
+ Prós
- • Deploy automático a cada push no GitHub
- • HTTPS gratuito e CDN global
- • Preview deploys por branch — ótimo pra testar features
- • Plano gratuito generoso pra projetos pessoais
− Contras
- • Sem backend — pra rodar o polling de odds, precisa de um servidor separado
- • Limites de bandwidth no plano free
Railway
Full-stack deploy com banco e cron jobs no mesmo lugar.
+ Prós
- • Roda backend Node.js junto com o front
- • Suporta cron jobs nativamente — ideal pro polling de odds
- • Banco PostgreSQL integrado
- • Logs e métricas no dashboard
− Contras
- • Plano gratuito com crédito limitado por mês
- • CDN menos robusta que Vercel pra assets estáticos
VPS (DigitalOcean / Hetzner)
Controle total do servidor. Mais trabalho, mais liberdade.
+ Prós
- • Controle total sobre infraestrutura e schedule
- • Custo fixo previsível — a partir de $4/mês
- • Roda qualquer coisa: cron, banco, scripts Python e Node juntos
− Contras
- • Você gerencia tudo: updates de segurança, SSL, monitoramento
- • Precisa configurar Nginx/Caddy como reverse proxy
- • Sem deploy automático — precisa configurar CI/CD manualmente
Pronto pra construir seus próprios dashboards?
Esse dashboard é um projeto de portfólio que impressiona em entrevistas. Mostra que você sabe React, TypeScript, integração com APIs, visualização de dados e deploy. Se quiser ir mais fundo, conecte com os modelos estatísticos do artigo de Poisson/ELO/xG e com o bot de Telegram pra ter um sistema completo de análise e alertas. A CrazyStack ensina essa stack inteira na prática.