A picks-only sports-market analytics platform.
Read-only odds ingestion · sharp-anchor devig & line shopping · trusted-CLV and source-quality tracking · evidence-first dashboard. You review every pick and place any bet yourself — the system never does.
Install · How it works · Sports · Validation · Dashboard · Configuration · Architecture · Docs
This system never places bets. It surfaces candidate picks for manual review; you decide and place any bet personally, on your own accounts.
There is no bet-execution path, no bookmaker login automation, no stored betting credentials, and no auto-betting flag — by design. Every market-data integration is read-only (GET). A CI safety audit (
scripts/safety_audit.sh) fails the build if a bet-placement path ever appears. Recommended stakes, edges and EV are informational only — betting involves risk and nothing here is a guarantee of profit.
The doctrine, in order of importance:
- Standalone models are not trusted just because they produce picks. The project's own backtesting found a goals model alone does not beat the market.
- Sharp-vs-soft line shopping is the core pricing idea. Fair value is priced from the sharpest available book (Pinnacle; optionally Betfair Exchange), the vig is stripped (8 named, 6 distinct parity-tested devig methods), and a candidate exists only when a softer book's price materially beats that fair value.
- Candidates are gated by evidence and freshness. A premium candidate without a real sharp anchor can be demoted to the shadow tier (
VALUE_REQUIRE_SHARP_ANCHOR); exchange anchors must clear a liquidity floor; odds older than the freshness window are discarded, never used (fail-closed). - CLV proves or disproves edge over time — and only trusted CLV counts. A closing line anchored by a pick's own fill book, an unmoved (tautological) line, a circular same-market close, or a fabricated/implausible close is excluded from evidence, not averaged in. Small-sample ROI is noise; all-row CLV is not proof. The quality bar for any claim is trusted CLV with a sufficient sample, acceptable freshness, source agreement, and reliable settlement.
Historical backtests (labeled historical — not live proof): an 18-league, 7-season sweep with a pre-registered holdout showed positive held-out CLV for the sharp-vs-soft method, with documented limitations (gross-fill optimism, correlated-sample SEs — since corrected to cluster-robust). That holdout is spent, and a later pre-registered single-shot on early-2026 data failed its sample-size bar due to a data-coverage anomaly, neither validating nor refuting the strategy. Details and caveats: docs/backtesting/ and ADR-0019. Live trusted-CLV accrual is the primary evidence path today, and it is not yet conclusive.
Provider-specific evidence: the historical backtests are method-level and were measured on OddsPortal-era book coverage and team-name forms. OddsPortal is therefore the fail-safe default. Switching to OddsChecker does not inherit that evidence — its book set, matching, and closing-line capture differ, so its live trusted-CLV starts accruing from zero. Until enough provider-specific closes and settlements accrue, OddsChecker picks remain unvalidated (shadow-first).
A sport is shown once it is scrapeable; it mints premium picks only where the pipeline is enabled — and any promotion beyond that requires an evidence review (trusted CLV, sample size, freshness, source agreement, settlement reliability), never an env flip.
| Sport | Status | Notes |
|---|---|---|
| Football / Soccer | ✅ Live pick source (benchmark) | The enabled pipeline. Historical held-out evidence supports the method; live trusted CLV is still accruing and not yet conclusive — stated on the dashboard, not hidden. |
| Basketball (incl. NBA) | Closest shadow candidate on current exploratory reports; not promotable (sample marginal, source-agreement coverage thin). | |
| Tennis (ATP / WTA) | 🚧 Shadow / display-only | Settlement now follows the pinnacle_one_set convention (see below); sharp-close capture evidence still accruing; no picks minted. |
| American football (NFL) | 🚧 Display-only | Too little event volume to evaluate yet. |
Both supported paths run the same code and serve the dashboard at http://localhost:8000/.
Docker Desktop runs the whole stack (app + Postgres + Redis) with one command:
git clone https://github.com/alexandrosh8/sharp-ev-picks.git
cd sharp-ev-picks
cp .env.example .env # Windows PowerShell: Copy-Item .env.example .env
# Set POSTGRES_PASSWORD, DASHBOARD_AUTH_PASSWORD_HASH and
# DASHBOARD_SESSION_SECRET in .env (generation commands are documented there).
docker compose --profile prod up -d --buildOpen http://localhost:8000/ and sign in with the password whose hash you provisioned. The container profile runs with APP_ENV=production, so it deliberately refuses blank/default credentials and does not expose first-run /setup. Stop with docker compose --profile prod down (data survives in a Docker volume); logs via docker compose --profile prod logs -f app.
The same Docker stack with restart: unless-stopped:
sudo apt install -y docker.io docker-compose-v2 git
sudo git clone https://github.com/alexandrosh8/sharp-ev-picks.git /opt/sharp-ev-picks
sudo chown -R $USER /opt/sharp-ev-picks
cd /opt/sharp-ev-picks
cp .env.example .env
chmod 600 .env
# edit .env: set COMPOSE_PROFILES=prod, POSTGRES_PASSWORD,
# DASHBOARD_AUTH_PASSWORD_HASH and DASHBOARD_SESSION_SECRET
docker compose up -d --buildReach it over an SSH tunnel (ssh -L 8000:127.0.0.1:8000 <vps>) or through an authenticated TLS reverse proxy. Production binds the app to loopback and rejects direct public binds. Full runbook: docs/deployment/openclaw-ubuntu.md.
brew install postgresql@16 redis
brew services start postgresql@16
brew services start redis
createdb betting_ai
uv sync --extra football --extra backfill # NBA: also --extra nba --extra models --extra ml
uv run alembic upgrade head
uv run uvicorn app.main:app --reload # http://localhost:8000/Prefer not to install the databases? docker compose up -d postgres redis and keep the app native. First-time commands live in docs/HOW_TO_RUN.md. Dev tasks:
uv run pytest -q # 3,600+ tests (no network)
uvx ruff check . # lint
uvx ruff format --check app tests scripts # formatting (CI-gated separately)
uv run mypy app tests # types
uv run alembic heads # single migration head
bash scripts/safety_audit.sh # no-autobet + secret-leak greps (CI-gated)Evidence/validation tooling (read-only; outputs refuse to overwrite prior runs):
uv run python scripts/research/sport_quality_report.py --days 30 # per-sport trusted-CLV / freshness / agreement report
uv run python scripts/research/devig_comparison.py # validation-only devig method comparison
uv run python scripts/bsp_inventory.py # BSP archive/cache inventory + readiness
uv run python scripts/arcadia_anchor_export.py export --from 2026-07-01 --to 2026-12-31
uv run python scripts/arcadia_anchor_export.py preflight --dataset <exported.csv> # prints PASS or DO-NOT-RUNThe next strategy validation is pre-registered, signed, and armed — and deliberately has not run:
- H2 protocol (signed): a pure prospective single-shot — no train side, no selection on any 2026 data, evaluated once against the pre-registered frozen configuration (hash-pinned) using the project's own Pinnacle ARCADIA capture as the independent sharp anchor and a future Betfair BSP archive as the close.
- H6 agreement gate (signed, tolerance 0.02): a validation/shadow-only variant requiring the sharp anchor to agree with an independent multi-book consensus; it records pass/fail/excluded reasons, never silently drops rows, and is not a live gate or promotion switch.
- Guards: the run is impossible until the future BSP data exists and a coverage preflight passes — the preflight prints
DO-NOT-RUNwhile data is incomplete (it currently does, correctly), spent-slate sha256 guards block every previously-read dataset, and a frozen-config-hash check stops the run if live settings drift. - No validation run has been performed early. Exploratory readouts are labeled exploratory/spent and are never used to tune frozen thresholds.
Evidence machinery accruing in the background: freshness-stratified trusted-CLV telemetry (anchor-age × mint-to-kickoff × sport × market buckets), monthly per-sport quality reports (coverage, agreement, freshness, settlement, sample sufficiency), and a pre-registered anchor-freshness bound (H8) that stays shadow-only. These reports exist to accrue meaningful samples over time, not to create same-day narratives.
A single self-contained page (app/api/dashboard.html; no framework, no CDN, installable PWA) styled as a trading-intelligence console. Five workspaces:
- Today — command screen: qualified picks, a derived "needs attention" queue (source degraded, low evidence, staleness), next kickoffs, recent results, and a one-line evidence position.
- Edges — master-detail pick console (stream → detail → evidence panes on desktop; full-screen sheets on mobile) with explicit trust states: Premium vs Shadow — tracked, informational (never styled actionable), stale, weak match, missing anchor, and per-pick close/CLV trust.
- Radar — market coverage by kickoff proximity, with DISPLAY-ONLY tags for unvalidated sports.
- Lab — the evidence workspace: a claims ledger ("can claim / cannot claim yet"), the trusted sharp-close CLV headline kept strictly separate from all-closes context CLV, close-quality exclusions (tautological / circular / fabricated), calibration, Sport Readiness (per-sport shadow status and blockers), and sample-size warnings everywhere.
- Sources — source-health matrix showing the active odds provider (OddsChecker / OddsPortal / The Odds API) and where the Betfair anchor comes from under it, plus ARCADIA, Betfair API monitor-only, and a proxy-pool panel (redacted) that auto-flags dead/quarantined slots and spare-capacity headroom; staleness monitor, review-queue counts, plain-language H2 validation readiness, and the H6 status line.
No performance claims appear on the dashboard; low-evidence and shadow items are visually incapable of looking actionable.
All secrets live in .env only (copy from .env.example; 0600, gitignored — never commit it). Local native development needs no external provider keys. The production Compose profile fails closed until its database and dashboard credentials are explicitly provisioned. The keys that matter most:
| Key | Default | What it does |
|---|---|---|
ODDS_SOURCE |
oddsportal |
Odds provider (switchable): oddsportal (free OddsPortal scrape, default), oddschecker (free scrape with inline Betfair Exchange; requires proxies), or odds_api (The Odds API). |
ODDSCHECKER_SPORTS |
soccer,basketball,tennis,american_football |
Which sports the OddsChecker feed polls (csv). |
ODDSCHECKER_CAPTURE_SHARP_MARKETS |
true |
Also capture every sharp-anchored (Betfair Exchange) prop/period/combo market as odds history (never priced or settled — pure capture). |
DASHBOARD_AUTH_ENABLED |
true in .env.example |
Production requires a pre-provisioned password hash and session secret. Loopback local mode may use one-time /setup. |
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID |
empty | Pick alerts. Blank just disables alerts; the dashboard still works. |
VALUE_REQUIRE_SHARP_ANCHOR |
true |
When true (default since 2026-07-07), a premium pick without a real Pinnacle/Betfair anchor demotes to shadow. |
SCRAPER_PROXY_POOL |
empty | Optional rotating proxies for the scrape and the Pinnacle close capture (see below). |
BETFAIR_EXCHANGE_ENABLED |
false |
Optional read-only Betfair Exchange BACK-odds capture as a second sharp anchor. |
ODDSPORTAL_USE_JSON_FEED |
false |
true swaps the per-match Playwright render for a faster curl_cffi JSON-feed reader. |
The full key reference (scrape tuning, timeouts, results-settlement cadence) is documented inline in .env.example.
Scrape proxies. The default OddsPortal scrape can run from the host IP; the optional OddsChecker source is Cloudflare-walled on datacenter-direct egress and therefore requires a rotating pool. A pool (host|port|user|pass quads, comma-separated) can widen coverage and speed a full slate. The dashboard flags dead/quarantined slots and shows headroom against active fetch concurrency. The same pool can serve the free Pinnacle ARCADIA close capture. Read-only either way; credentials never leave .env. On heavy slates, limited capture/proxy capacity can constrain coverage — the freshness gate then discards stale candidates rather than minting stale picks.
Betfair Exchange. An off-by-default, read-only capture (ADR-0015) binds Betfair BACK odds inline on the same canonical event as the soft books. Exchange anchors are liquidity-gated. The separate Betfair API staleness comparison is monitor-only — it records verdicts and never demotes live picks.
Proven open-source engines bound into one pipeline:
- Ingestion — pluggable odds provider (
ODDS_SOURCE): the default OddsHarvester-based OddsPortal scrape (app/ingestion/oddsportal.py, Playwright render orcurl_cffiJSON feed), the optional OddsChecker reader (app/ingestion/oddschecker.py, read-onlycurl_cffi/Hypernova JSON, GET-only, with Betfair Exchange + Sportsbook inline), or The Odds API; free Pinnacle ARCADIA close capture (app/ingestion/pinnacle_arcadia.py); optional dedicated Betfair Exchange BACK odds (OddsPortal source only); Betfair BSP archive tooling for validation. All read-only. OddsChecker requiresSCRAPER_PROXY_POOL. - Pricing — penaltyblog Dixon-Coles for football (
app/models/football_dc.py); an 8-method devig (app/probabilities/devig.py— multiplicative, additive, power, Shin closed-form, probit, odds-ratio, logarithmic, differential-margin; 6 distinct estimators once proven equivalences are collapsed; parity-tested, with cross-library golden vectors). - Edge & risk — edge/EV gating (
app/edge/value.py) with sharp/consensus anchor grading and an exchange-liquidity floor; fractional-Kelly sizing (informational) with per-pick and daily exposure caps (app/risk/). - Resolution / matching — a precision-hardened cross-source matcher (
app/resolution/): marker-aware (women/youth/reserve/B sides never collapse onto the senior team), two-tier Jaro-Winkler over a curated alias seed, tennis surname-initial veto, tight kickoff windows, fail-closed on ambiguity, plus a read-only wrong-game self-audit each cycle. Aliases are applied only through a sanctioned evidence process: distinct-fixture co-occurrence evidence, dry-run patch review, regression tests, and a matcher differential proving zero unintended merges — generic-base aliases are never forced. - Settlement — automatic from free results feeds with wrong-game guards; tennis follows the declared
pinnacle_one_setconvention (below); anything unclassifiable is left for manual entry, never guessed. - Evidence & validation — trusted-CLV true-up with independence/tautology/circularity exclusions; the pre-registered ARCADIA/BSP validation harness (
app/backtesting/,scripts/arcadia_anchor_export.py) with preflight DO-NOT-RUN and spent-data guards; per-sport quality reporting. - Persistence & serving — Postgres warehouse (SQLAlchemy 2.0 async + Alembic); APScheduler drives polling, settlement, CLV true-up and sharp-close captures; FastAPI serves SignalDesk.
- Agent/skills discipline — project skills under
.claude/skills/encode the research, shadow-engineering, matching and CLV-evidence procedures used to maintain the repo.
Stack: Python 3.12 · FastAPI · SQLAlchemy 2.0 async + asyncpg · APScheduler · Redis · PostgreSQL · Playwright (Chromium) · Docker Compose. Pure-math modules (probabilities, edge, risk) take no env/DB/HTTP — policies enter as frozen dataclasses at the composition root.
Tennis settlement convention (pinnacle_one_set, matching the sharp-anchor book's rule): a walkover or any abnormal ending before one completed set voids all markets; a retirement after at least one completed set grades the moneyline to the advancing player and voids other markets; unclassifiable cases remain unsettled for manual entry. This improves settlement reliability — it does not promote tennis.
The project is in monitor-and-accrue mode: production is monitored, the validation machinery is signed and armed, and evidence is accruing. Clean monitoring rounds with no changes are the expected outcome. No user-side action is needed unless operational capture capacity changes.
Latest hardening pass (2026-07-09, adversarially-verified audit): settlement dedup now folds cross-source selection-spelling variants (line-preserving); CLV close provenance gained line-ambiguity and period-submarket guards; pick persist + daily-exposure reserve is cancellation-atomic; model-strategy EV/Kelly nets exchange commission; the dashboard fails closed on health/tier-fetch failures and refuses structurally-inconsistent rows; cross-source matching measures ambiguity against the first distinct rival and quarantines conflicting alias claims. All shipped test-first with the safety audit and the locked parse-replay scorer green.
Current limitations (honest): trusted-CLV samples are still accruing everywhere and are not yet conclusive for any sport, including the live football pipeline; basketball is the closest shadow candidate but not promotable; NFL lacks volume; tennis settlement is fixed but capture evidence is thin; Betfair staleness stays monitor-only; the H2 validation waits for future BSP data and a preflight PASS; heavy slates can exceed capture capacity, in which case freshness gates fail closed.
Next evidence milestones: monthly sport-quality reports; trusted-CLV accrual per sport/market/freshness bucket; source-agreement coverage; settlement reliability confirmation (first live tennis retirement case); the H2 prospective validation when its data exists.
Use the local repository as the source of truth. Do not loosen gates, promote sports without an evidence review, run the H2 validation early, make performance claims, or add any bet-execution path. Prefer tests and evidence over speculative code. The quality bar is trusted CLV, freshness, source agreement, sample size, and settlement reliability. A clean monitoring round with no changes is a valid, successful outcome.
| Path | Contents |
|---|---|
docs/adr/ |
Architecture decision records, including ADR-0019 (pre-registered validation protocol, signed amendments) |
docs/runbooks/ |
Operational runbooks, including the H2 validation single-shot procedure |
docs/research/ |
Research logs, sport-quality reports, BSP readiness, devig comparisons |
docs/backtesting/ |
Historical backtesting methodology & results (spent/exploratory — see caveats inline) |
docs/deployment/ |
Mac dev + Ubuntu/OpenClaw deployment guides |
docs/security/ |
Security notes & reviews |
docs/HOW_TO_RUN.md |
End-to-end verify-the-backtest & live-picks commands |
MIT © 2026 alexandrosh8 — free to use, modify, and distribute; provided "as is", without warranty.