Florida Lottery Intelligence
Market Overview
Real-time analytics engine — updated daily at 6:00 AM ET
Best EV Game
—
calculating...
Best Score
—
weighted rank
Top Prizes Left
—
games w/ jackpot intact
Avg Portfolio ROI
—
across all scratch-offs
FL Jackpot Pool
—
scratch-offs only
Powerball
$325M
Wed & Sat draw
Expected Value Ranking (EV/$ticket)
Top Prize % Remaining by Game
Today's Best Plays
Draw Game Jackpots
Florida Scratch-Off Games
Scratch-Off Analyzer
Draw Games Analysis
Pick 2 → Powerball
Statistical Deep Dive
Capital Asset Modeling
Correlation · Cointegration · Drawdown · Sharpe · Kelly · Bell Curves
Game ROI Correlation Matrix (top 8 games)
Measures EV return correlation between games by prize tier overlap. Higher = similar risk/reward profile. Use for portfolio diversification.
Price vs EV Scatter — All Games
Each dot = one game. Dots above the diagonal line = positive EV relative to price.
Cointegration Analysis — EV Spread by Price Tier
Cointegration indicates long-run equilibrium between prize structures. When spread reverts to mean, EV improves. This simulates pair-trading logic applied to scratch-off prize tiers.
Lottery Sharpe Ratio (Top 10 Games)
Sharpe = (EV − RiskFree) ÷ StdDev(prize distribution). Higher = better risk-adjusted return. Risk-free set to $0 (no-play baseline).
Maximum Drawdown Simulation (200 ticket sessions)
Simulated cumulative P&L over 200 ticket purchases. Drawdown = peak-to-trough decline. Helps model bankroll volatility risk.
Risk-Adjusted Metrics Table
Prize Distribution Bell Curve — Select Game
Expected Outcome Distribution — 50 Ticket Simulation
Kelly Criterion Bet Sizing
Kelly Formula: f* = (bp − q) / b
where b = net odds, p = win prob, q = 1−p
Note: Kelly fractions are typically very small in lottery — this tool uses fractional Kelly (0.1×) for realistic bankroll sizing.
where b = net odds, p = win prob, q = 1−p
Note: Kelly fractions are typically very small in lottery — this tool uses fractional Kelly (0.1×) for realistic bankroll sizing.
Kelly Bankroll Growth Simulation
EV vs ROI Bubble Chart
Bubble size = ticket price. X = expected value. Y = ROI%. Upper-right = best plays.
Prize Tier Breakdown — Donut Charts
EV CONTRIBUTION %
PRIZES REMAINING %
Play Strategy Engine
Budget Optimizer
Kelly-weighted recommendations by budget allocation
Configure Session
QUICK SELECT
Budget Allocation Pie
Area Intelligence — Where to Play
Loading area recommendations...
Frequency Analysis Laboratory
Hot / Cold Number Engine
Historical frequency analysis with probability distributions
Number Frequency Heatmap — Last 500 Draws
Frequency Bar Chart with Probability %
🔥 HOT NUMBERS
❄ COLD NUMBERS
⚡ DUE NUMBERS
Note: "Due" is a statistical observation, not a predictor. Each draw is independent.
Number Pair Co-occurrence Frequency
Rolling Window Analysis (last 20/50/100 draws)
AI Number Suggestion Engine
Loading suggestions...
Data Infrastructure
Scraper + API Integration
Production-ready scrapers for Florida Lottery data feeds
Node.js + Puppeteer / Cheerio
// LottoEdge FL — Node.js Scraper (Puppeteer + Cheerio) // Scrapes: floridalottery.com/games/scratch-offs + draw results // Run daily via cron: 0 6 * * * node scraper.js const puppeteer = require('puppeteer'); const cheerio = require('cheerio'); const axios = require('axios'); const fs = require('fs'); const path = require('path'); // ── CONFIG ────────────────────────────────────────────── const CONFIG = { baseUrl: 'https://floridalottery.com', scratchUrl: 'https://floridalottery.com/games/scratch-offs', drawUrl: 'https://floridalottery.com/games/draw-games', gameIds: [1555,1562,1590,1594,1597,1604,1606, 1608,1618,1619,1620,1621,1622,1623,1627], outputDir: './data', delay: 1500 // ms between requests (be respectful!) }; // ── SCRAPE SINGLE SCRATCH GAME ────────────────────────── async function scrapeScratchGame(gameId) { const url = `${CONFIG.baseUrl}/games/scratch-offs/view?id=${gameId}`; const res = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; LottoEdgeBot/1.0)' } }); const $ = cheerio.load(res.data); const prizes = []; // Parse prize table $('.odds-prizes-table tr, table tr').each((i, row) => { const cells = $(row).find('td'); if (cells.length >= 3) { const prizeText = $(cells[0]).text().replace(/[$,]/g,'').trim(); const oddsText = $(cells[1]).text().match(/[\d,]+$/)?.[0] || '0'; const remText = $(cells[2]).text().trim(); const [rem, total] = remText.split(' of ').map(x => parseInt(x.replace(/,/g,''))); if (isNaN(rem)) return; prizes.push({ prize: +prizeText, odds: +oddsText.replace(/,/g,''), rem, total }); } }); const topPrize = $('[data-top-prize], .top-prize').first().text().replace(/[$,]/g,'').trim(); const topRemaining = $('.top-prizes-remaining').first().text().trim(); const overallOdds = $('.overall-odds').first().text().replace('1:','').trim(); return { gameId, topPrize: +topPrize || 0, topRemaining, overallOdds, prizes, scrapedAt: new Date().toISOString() }; } // ── SCRAPE ALL GAMES ───────────────────────────────────── async function scrapeAll() { console.log('[LottoEdge] Starting scrape...', new Date().toISOString()); const results = {}; for (const id of CONFIG.gameIds) { try { results[id] = await scrapeScratchGame(id); console.log(` ✓ Game #${id} — ${results[id].prizes.length} prize tiers`); } catch (e) { console.error(` ✗ Game #${id}:`, e.message); } await new Promise(r => setTimeout(r, CONFIG.delay)); } const outPath = path.join(CONFIG.outputDir, `scratch_${Date.now()}.json`); fs.writeFileSync(outPath, JSON.stringify(results, null, 2)); console.log(`[LottoEdge] Saved to ${outPath}`); return results; } // ── SCRAPE DRAW RESULTS ────────────────────────────────── async function scrapeDrawResults() { const browser = await puppeteer.launch({ headless: 'new' }); const page = await browser.newPage(); await page.goto('https://floridalottery.com/games/draw-games/winning-numbers'); await page.waitForSelector('.winning-numbers', { timeout: 10000 }); const drawData = await page.evaluate(() => { const results = []; document.querySelectorAll('.draw-result-row').forEach(row => { const game = row.querySelector('.game-name')?.textContent; const date = row.querySelector('.draw-date')?.textContent; const nums = [...row.querySelectorAll('.ball')].map(b => +b.textContent); results.push({ game, date, numbers: nums }); }); return results; }); await browser.close(); return drawData; } scrapeAll().then(data => console.log('Complete', Object.keys(data).length, 'games'));
Python + BeautifulSoup + Selenium
# LottoEdge FL — Python Scraper # pip install requests beautifulsoup4 selenium pandas schedule import requests, json, time, schedule, logging from datetime import datetime from bs4 import BeautifulSoup import pandas as pd logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') BASE_URL = "https://floridalottery.com" GAME_IDS = [1555,1562,1590,1594,1597,1604,1606, 1608,1618,1619,1620,1621,1622,1623,1627] HEADERS = {"User-Agent": "LottoEdge/1.0 (educational research)"} def scrape_game(game_id: int) -> dict: url = f"{BASE_URL}/games/scratch-offs/view?id={game_id}" r = requests.get(url, headers=HEADERS, timeout=10) r.raise_for_status() soup = BeautifulSoup(r.text, "html.parser") prizes = [] for row in soup.select("table tr"): cells = row.find_all("td") if len(cells) < 3: continue prize_str = cells[0].get_text().replace("$","").replace(",","").strip() odds_str = cells[1].get_text().replace(",","").strip() rem_str = cells[2].get_text().strip() try: rem, total = [int(x.replace(",","")) for x in rem_str.split(" of ")] odds_val = int(odds_str.split("-in-")[-1]) prizes.append({"prize": int(prize_str), "odds": odds_val, "rem": rem, "total": total}) except: continue return {"game_id": game_id, "prizes": prizes, "scraped_at": datetime.utcnow().isoformat()} def calc_ev(game: dict) -> float: return sum(p["prize"] * (1/p["odds"]) * (p["rem"]/p["total"]) for p in game["prizes"]) def daily_scrape(): logging.info("Starting daily FL Lottery scrape...") results = {} for gid in GAME_IDS: try: results[gid] = scrape_game(gid) ev = calc_ev(results[gid]) logging.info(f" Game #{gid}: EV=${ev:.2f}, {len(results[gid]['prizes'])} tiers") time.sleep(1.5) except Exception as e: logging.error(f" Error game #{gid}: {e}") fname = f"data/scratch_{int(time.time())}.json" with open(fname, "w") as f: json.dump(results, f, indent=2) logging.info(f"Saved {len(results)} games to {fname}") # Schedule daily at 6 AM ET schedule.every().day.at("06:00").do(daily_scrape) if __name__ == "__main__": daily_scrape() # run once immediately while True: schedule.run_pending(); time.sleep(60)
REST API Specification
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/games | All active scratch games |
| GET | /api/games/:id | Game detail + prize structure |
| GET | /api/ev/:id | Expected value calculation |
| GET | /api/rankings | Ranked game list by score |
| GET | /api/draw/:game | Draw game history + hot/cold |
| GET | /api/budget/:amount | Optimized play plan |
| POST | /api/simulate | Monte Carlo simulation |
| POST | /api/alerts | Set prize-remaining alert |
// Express.js API server — app.js const express = require('express'); const { scrapeAll, calcEV } = require('./scraper'); const app = express(); app.use(express.json()); // Auth middleware const auth = (req, res, next) => { const key = req.headers['x-api-key']; if (!key || !validKeys.has(key)) return res.status(401).json({ error: 'Invalid API key' }); next(); }; app.get('/api/games', auth, async (req, res) => { const data = loadLatestData(); res.json(Object.values(data).map(g => ({ id: g.gameId, ev: calcEV(g).toFixed(2), ...g }))); }); app.get('/api/budget/:amount', auth, (req, res) => { const b = +req.params.amount; res.json(optimizeBudget(b, loadLatestData())); }); app.post('/api/simulate', auth, (req, res) => { const { gameId, tickets, sims } = req.body; res.json(monteCarlo(gameId, tickets, sims || 10000)); }); app.listen(3000, () => console.log('LottoEdge API running on :3000'));
Automated Daily Pipeline
# crontab -e — add this line for daily 6 AM ET scrape: # 0 11 * * * cd /app && node scraper.js >> logs/scrape.log 2>&1 # GitHub Actions workflow (.github/workflows/daily-scrape.yml): --- name: Daily FL Lottery Scrape on: schedule: - cron: '0 11 * * *' # 6 AM ET = 11 UTC workflow_dispatch: # allow manual trigger jobs: scrape: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm ci - run: node scraper.js env: DB_URL: ${{ secrets.DATABASE_URL }} WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK }} - uses: actions/upload-artifact@v4 with: name: scrape-data path: data/*.json retention-days: 30 # Docker Compose for full stack: --- version: '3.8' services: api: build: . ports: ["3000:3000"] environment: - DB_URL=postgresql://postgres:pass@db:5432/lottoedge db: image: postgres:16 volumes: [pgdata:/var/lib/postgresql/data] scheduler: build: . command: node scheduler.js depends_on: [db] volumes: pgdata:
Webhooks & Alert System
// Alert system — sends Discord/Slack/Email when key events occur const alerts = { // Trigger: top prize count drops (someone won!) topPrizeClaimed: async (gameId, prizeTier, newCount) => { const msg = { embeds: [{ title: `🎰 TOP PRIZE CLAIMED — Game #${gameId}`, description: `$${prizeTier.toLocaleString()} prize won! **${newCount} remaining**`, color: 0xFF4D6D, timestamp: new Date() }] }; await axios.post(process.env.DISCORD_WEBHOOK, msg); }, // Trigger: EV crosses threshold (game becomes better value) evThreshold: async (gameId, ev, price) => { if (ev / price > 0.85) { // 85%+ return threshold await axios.post(process.env.SLACK_WEBHOOK, { text: `⚡ HIGH EV ALERT: Game #${gameId} at $${ev.toFixed(2)} EV on $${price} ticket` }); } }, // Daily digest email via SendGrid dailyDigest: async (topPlays) => { await sgMail.send({ to: process.env.ALERT_EMAIL, from: 'alerts@lottoedge.com', subject: `LottoEdge Daily Brief — ${new Date().toDateString()}`, html: buildEmailHTML(topPlays) }); } }; // Check for changes after each scrape async function detectChanges(prev, curr) { for (const [id, game] of Object.entries(curr)) { const p = prev[id]; if (!p) continue; // Top prize claimed? if (p.topPrizeCount > game.topPrizeCount) await alerts.topPrizeClaimed(id, game.topPrize, game.topPrizeCount); // EV improvement? await alerts.evThreshold(id, calcEV(game), game.price); } }
SaaS Subscription Tiers
Choose Your Edge
Professional-grade lottery intelligence for serious players
All subscriptions include 7-day free trial · Cancel anytime · 18+ only · Gambling involves risk · Play responsibly