# Sports Predictor — setup

Pipeline: fixtures/standings -> strength-Poisson model -> picks (+EV vs bookie odds) -> Telegram.

## 1. Keys (optional except Telegram)
Copy `.env.example` -> set in your profile env or shell environment:

- `ODDS_API_KEY`  — free at https://the-odds-api.com (500 req/mo). Enables
  England/Spain/Italy/France fixtures + bookmaker odds for the EV layer.
  Bundlesliga works WITHOUT it (OpenLigaDB, keyless).
- `TELEGRAM_BOT_TOKEN` — new bot from BotFather (own token per bot, never reuse).
- `TELEGRAM_CHAT_ID`  — your chat id (send a message to the bot, then
  `https://api.telegram.org/bot<TOKEN>/getUpdates` -> message.chat.id).

## 2. Runs
```bash
python3 ~/.hermes/scripts/sports-predictor/backtest.py        # honesty check over history
python3 ~/.hermes/scripts/sports-predictor/picks.py           # today's picks + EV
```

## World.xyz (binary prediction markets)

- `picks.py` prices every upcoming fixture into binary markets (home/away win,
  over/under 2.5, BTTS) and writes `world_prices.json`.
- The market catalog API is Cloudflare-gated to the world.xyz frontend (403 even
  via Firecrawl/scrape.do stealth) — so you READ the YES price in Phantom and
  check it: `python3 world_check.py "Team A vs Team B" <yes_price> [market]`
- Buy rule: YES <= fair-0.07, NO >= fair+0.07 (0.05 edge + 0.02 dealer spread).
- Trust model (chainstacklabs/world-xyz-research): single operator key resolves
  markets (Chainlink off-chain, not verified on-chain, no dispute window);
  Token-2022 permanent delegate can seize positions; all programs upgradeable;
  CASH (Bridge) holds freeze/clawback. Football outcomes are unambiguous so
  misresolution risk is low — but size small stakes accordingly.

## Match context (statsfeed.py — Digitain/StatsInfo)

- `statsfeed.py` talks to the Livescore2 API behind betfa's stats widget:
  `d3skketn98dcjt.cloudfront.net/apiv3/` with `o=dt&guid=c0ad5b29-...`.
  Endpoints: `feed/1/<YYYY-MM-DD>/`, `game/1/<id>/`, `h2h/1/<minId>vs<maxId>/`.
- Builds per-fixture context (last-5 form from daily feeds, H2H summary, notes)
  → `## Match context` section in the report + `app_context.json` for the app.
- Team matching: accent-strip norm → aliases (Deportivo/Athletic/Betis) →
  unique-best fuzzy (ratio>=0.72, margin 0.05). Failures become a note, never
  an error — context is bonus data and must never break the pipeline.
- Form is DISPLAY-ONLY. form_backtest.py verdict (7,276 matches 2022-26,
  6,943 evaluated, rolling no-lookahead): +form −0.63pp acc / worse Brier;
  +H2H +0.07pp / worse PL → both REJECTED. Do not re-add these features
  without a materially different design.

## Hosting (Telegram mini-app)

- `app_server.py` serves `app/` + `app_data.json` + `app_results.json` on
  127.0.0.1:8788 (systemd **user** unit `sports-app.service`).
- Public: **https://yek.nokhod.online** — Caddy (system unit `caddy.service`,
  config `/etc/caddy/Caddyfile`) reverse-proxies 443 → 8788 and holds the
  Let's Encrypt cert (auto-renew, storage /var/lib/caddy).
- DNS: A record `yek.nokhod.online → 136.148.67.219` (Cloudflare DNS, grey cloud).
- Bot menu button points to https://yek.nokhod.online/app/index.html.
- The old trycloudflare quick-tunnel + URL watcher are retired (URL rotated
  daily). `tunnel_watch.py` kept only as historical reference.
- Data flow: `run_daily.sh` → audit.py (grades yesterday, rewrites
  app_results.json) → picks.py (rewrites app_data.json).

## Cron (daily picks)
```
30 5 * * *  cd ~/.hermes/scripts/sports-predictor && ODDS_API_KEY=... TELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=... python3 picks.py >> picks.log 2>&1
```
05:30 UTC = 09:00 Tehran. Add `--new` if the model changes: rerun backtest first.

## Honesty rules
- Backtest stands on matches already kicked off ONLY (no lookahead).
- "fair-odds P&L" is hypothetical (no vig); real edge only appears vs actual
  market odds (EV layer). Bet only when EV > 4% and stakes per Kelly cap 5%.
- Early-season predictions are low-confidence (small PPG samples). Treat
  prob >= 55% as "confident", not "safe".