Scan Hard Rock NFL spreads against a sharp reference (Pinnacle), compute no‑vig probabilities at your exact line (with pushes), size via Kelly, and post concise +EV picks to Discord.
- Markets: Hard Rock spreads (point handicaps) with American prices.
- Reference: Pinnacle spreads with alternate lines (ATS). De‑vig each line to fair probabilities.
- Mapping: Interpolate Pinnacle’s alt‑line ladder to Hard Rock’s exact spread; account for pushes on whole numbers.
- EV: Expected value per $1 stake; dynamic EV threshold based on mapping risk.
- Sizing: Kelly fraction with push probability; cap by
MAX_UNIT. - Persistence: Signals saved to SQLite (
/data/bets.sqlite). - Scheduling: Optimized in‑app schedule to fit monthly poll budget; no host cron needed.
- Notifications: Discord webhook with readable, compact format.
By default the bot installs an optimized weekly schedule (~124 polls/month) focused on the highest‑signal windows while staying within a tighter credit budget:
- Sunday 1pm slate: Inactives 11:30–11:45 (4 polls), final 12:30–12:59 (6)
- Sunday 4:25pm slate: Inactives 14:55–15:10 (3), final 16:05–16:25 (2)
- SNF/MNF/TNF: 3 polls each T−75 to T−10
- Sunday night openers: 3 polls 20:00–23:00
- Friday status: 2 polls 15:30–18:30
- Saturday limits rise: 2 polls 18:00–23:30
Behavior:
- Five‑Sunday months: buckets scale ~20% down automatically.
- Banking: if the Hard Rock board hasn’t moved since the last sample, skip the next scheduled poll to preserve budget.
Controls:
USE_OPT_SCHEDULE=0switches to a simple legacy schedule withWEEKDAY_RUN_TIMEandSUNDAY_RUN_TIME.
Pull the image published by GitHub Actions to GitHub Container Registry (GHCR).
docker pull ghcr.io/<owner>/<repo>:latest
# Example if your repo is github.com/acme/nflbot
# docker pull ghcr.io/acme/nflbot:latestThen run it as shown below in Step 4, replacing the image name with
ghcr.io/<owner>/<repo>:<tag> (e.g., latest or a version tag).
Notes:
- Repositories are published to GHCR by the workflow on pushes to
mainand tagsv*.*.*. - If the package is private, authenticate first:
echo $GITHUB_TOKEN | docker login ghcr.io -u <your-username> --password-stdin.
docker build -t nflbot:0.1.0 .Set DISCORD_WEBHOOK_URL to a Discord webhook before running the bot:
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."Set THEODDSAPI with your API key. The bot fetches Pinnacle spreads (with alt lines) and removes vig to compute fair probabilities. If this fetch fails or quota is exhausted, the run exits early (optionally sends a “Quota” notice to Discord) and will resume on the next scheduled poll.
export THEODDSAPI="<your_api_key>"Mount a host directory for the SQLite ledger at /data and pass the webhook
URL (and any tuning variables) as environment variables.
docker run --name nflbot \
-e DISCORD_WEBHOOK_URL="$DISCORD_WEBHOOK_URL" \
-e BANKROLL=500 -e MIN_EDGE=0.03 -e MIN_EDGE_ML=0.03 -e KELLY_FRACTION=0.5 -e MAX_UNIT=0.02 \
-e USE_OPT_SCHEDULE=1 -e WEEKDAY_RUN_TIME=09:00 -e SUNDAY_RUN_TIME=12:00 \
-e THEODDSAPI="${THEODDSAPI:-}" \
-v $(pwd)/data:/data \
--restart unless-stopped \
nflbot:0.1.0The bot self-schedules using the schedule library and does not require a host
cron or compose setup. Logs print to stdout.
DISCORD_WEBHOOK_URL: Discord webhook (required to send alerts).BANKROLL: Total bankroll in dollars (default500).MIN_EDGE: Base EV threshold (decimal). Default0.03(3%). Values below 3% are clamped.MIN_EDGE_ML: Moneyline EV threshold. Default0.03(3%). Values below 3% are clamped.KELLY_FRACTION: Fractional Kelly to apply (default0.5).MAX_UNIT: Max stake as fraction of bankroll (default0.02).MAX_INTERP_GAP: Max points away for interpolation (default1.0).USE_OPT_SCHEDULE: Enable optimized weekly schedule (default1).WEEKDAY_RUN_TIME/SUNDAY_RUN_TIME: Legacy schedule times inHH:MM.THEODDSAPI: API key for Pinnacle reference prices.RUN_ONCE: If truthy, runs once and exits.
Notes:
- Dynamic EV thresholding raises the bar to ~3.5–4.0% in riskier mappings (whole numbers or interpolations near 3/7).
- Signals are de‑duplicated in SQLite via a unique key.
SQLite ledger is written to /data/bets.sqlite. Mount /data to persist.
Each alert is a compact three‑line block:
SF @ JAX — Sun 13:00 👊 +0.7% EV
* Pick: JAX +7.5 at -115 (Hard Rock)
* Stake: $5.00
Identical signals are de-duplicated in the ledger using a unique key on
(game_id, market, pick, odds). Repeated runs that encounter the same price
for the same side will not create duplicate rows.
- Install:
pip install -r app/requirements.txt - Run once:
RUN_ONCE=1 LOG_LEVEL=INFO python -m app.main - Tests:
pytest -q
Notes:
- Quota handling: if The Odds API returns 402/429 or equivalent, the run exits early and posts a concise “Quota” message to Discord (if configured).
- Logging includes a brief per‑game note when EV is unavailable due to insufficient Pinnacle alt lines within
MAX_INTERP_GAP.