diff --git a/CHANGELOG.md b/CHANGELOG.md index 22d20b3..e728d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ to bump. Every change to API paths or response schemas gets a one-line entry her the [OpenAPI Version Check](.github/workflows/openapi-version-check.yml) CI job enforces that a bump has a matching entry. +## 3.2.0 — 2026-07-14 + +- Add `quality_tier` to the `EVOpportunity` schema (`/opportunities/ev`) — a grader-certified quality label (`A` / `B` / `C`) stamped on every opportunity. `A` = corrected EV ≥ 4pp (≥ 6pp props), Pinnacle-anchored, prematch, and an unflagged segment (the consistently-positive graded-ROI class); `B` = EV ≥ 2pp (≥ 4pp props) not exclusion-flagged; `C` = everything else, plus any exclusion-flagged segment regardless of EV. Read-only (no query filter); omitted on suspended rows (`is_suspended`). Additive, backward-compatible. sharp-api-go #1204. + ## 3.1.0 — 2026-06-02 - Add `player_id` to the `EVOpportunity` schema (`/opportunities/ev`) — the canonical cross-book player identifier resolved by atlas_players (e.g. `baseball_mlb_corbin_carroll`). Nullable and always present alongside `player_name`; `null` until the producer flips a book to active player-id capture. Consumers should group player props on `player_id` and fall back to `player_name`. Additive, backward-compatible. sharp-api-go #213. diff --git a/content/en/api-reference/opportunities-ev.mdx b/content/en/api-reference/opportunities-ev.mdx index 67ec413..e442baa 100644 --- a/content/en/api-reference/opportunities-ev.mdx +++ b/content/en/api-reference/opportunities-ev.mdx @@ -345,6 +345,7 @@ No key supplied returns `missing_api_key`; a bad key returns `invalid_api_key`. | `est_correction_s` | number\|null | Estimated seconds until the bet-side book corrects toward the sharp move (P50 follow-lag for this sportsbook+sport). `null` when no sample is available. | | `warnings` | string[] | Data quality warnings (e.g., `POTENTIALLY_STALE_ODDS`, `LIVE_STALE_ODDS`, `SINGLE_SHARP_REF`, `SINGLE_SHARP_PERIOD`) | | `single_sharp_period` | boolean | `true` when only one sharp book priced the line **and** the market is a period/derivative sub-slice (innings, halves, quarters, sets). Filterable flag for phantom-suspect period EV; mirrors the `SINGLE_SHARP_PERIOD` warning. | +| `quality_tier` | string\|undefined | Grader-certified quality label — `A` (bet-this), `B` (edge), or `C` (informational). Present on every opportunity; **omitted** on suspended rows (`is_suspended`). Output-only — there is **no** `quality_tier` query filter. See [Quality Tier](#quality-tier) below for the criteria. | | `is_suspended` | boolean\|undefined | `true` (omitted otherwise) when the sharp reference is currently suspended live: the bet is still offerable but the edge is unknown, so `ev_percentage`, `fair_probability`, `no_vig_odds`, `kelly_percent`, and `market_width` are `null`ed rather than shown stale. `suspended_since` (Unix epoch seconds) accompanies it. | | `detected_at` | string | ISO 8601 timestamp when the +EV was first detected | | `odds_probability` | number | Implied probability of `odds_american` (vig-included) | @@ -431,6 +432,33 @@ When soft books are slow to update their odds after a market move, their implied We recommend setting `min_ev=2` for most use cases. Marginal EV (below 2%) can be eroded by line movement before you place the bet. +## Quality Tier + +Every opportunity carries a `quality_tier` — a grader-certified `A` / `B` / `C` label that supersedes the rough [EV Thresholds](#ev-thresholds) heuristic above with evidence from SharpAPI's historical grading ledger. It answers *"how much should I trust this edge?"*, not just *"how big is the stated EV?"* + +| Tier | Meaning | Criteria | +|------|---------|----------| +| **A** | **Bet this** | `ev_percentage` ≥ 4 (≥ 6 for player props), Pinnacle-anchored (`sharp_book: "pinnacle"`), **prematch** (`is_live: false`), and the `(book, sport, market)` segment is **not** flagged by our nightly reliability model. Historically the only class with consistently positive graded ROI. | +| **B** | **Edge** | `ev_percentage` ≥ 2 (≥ 4 for props) and the segment is not exclusion-flagged. Includes live, non-Pinnacle-anchored, and watch-flagged rows that clear the band — historically around break-even. | +| **C** | **Informational** | Everything else, plus **any** exclusion-flagged segment regardless of EV. Disclosed as market intelligence, not a recommended bet. | + + +`quality_tier` is **output-only** — there is no `quality_tier` query parameter. Filter client-side. `min_ev` / `live` / `sportsbook` are only partial proxies: `min_ev` alone does not reproduce a tier, because the sharp anchor, prematch/live state, and segment reliability also gate it. + + +**A high `ev_percentage` does not guarantee tier A.** Two prematch, Pinnacle-anchored baseball totals from the *same* book illustrate why: + +| Selection | `ev_percentage` | `quality_tier` | Why | +|-----------|-----------------|----------------|-----| +| 1st 5 Innings Total Runs — Over 5 | 7.35 | **A** | Segment unflagged | +| Total Runs — Over 9 | 7.94 | **B** | *Higher* EV, but the full-game `total_runs` segment is watch-flagged for historically weak results | + +The higher-EV row is **B**, not A, because our reliability model has seen that segment underperform — exactly the "big EV number that's actually the artifact" the tiering is designed to demote. See [EV Calculation → Quality Tiers](/en/concepts/ev-calculation/#quality-tiers) for the grading rationale behind the bands. + + +A tier is **not** a profit guarantee. Even tier A carries variance (see [Kelly Criterion](#kelly-criterion) and the Variance Warning below). Suspended rows (`is_suspended: true`) carry **no** tier — their edge is unknown, so no quality claim is made. + + ## Kelly Criterion The `kelly_percent` field is the optimal **percentage** of your bankroll (0–100) to wager according to the full Kelly criterion. A value of `2.1` means full Kelly recommends 2.1% of bankroll; a value of `34.4` means 34.4%. diff --git a/content/en/concepts/ev-calculation.mdx b/content/en/concepts/ev-calculation.mdx index 6965a89..3a7dc77 100644 --- a/content/en/concepts/ev-calculation.mdx +++ b/content/en/concepts/ev-calculation.mdx @@ -98,6 +98,33 @@ When soft books are slow to adjust, their odds imply a lower probability than re SharpAPI's default threshold is 2% EV. Adjust with the `threshold` parameter. +## Quality Tiers + +The EV Thresholds table above is a rough heuristic. In practice **stated EV and realized results diverge**, so SharpAPI stamps every +EV opportunity with a grader-certified `quality_tier` (`A` / `B` / `C`) derived from its historical grading ledger — hundreds of thousands of picks scored against closing lines and settled outcomes. + +### Why a tier, not just a number + +When picks are graded against real results, realized ROI generally rises with stated EV — but the **low bands bleed**. The bulk of published volume sits in the sub-2% EV band, which has historically graded *negative* once vig and post-detection line movement are accounted for; the 2–4% band is roughly break-even. Durable, repeatable edge concentrates where a large stated EV is corroborated by the sharpest reference (Pinnacle), on a **prematch** market, in a segment that hasn't historically underperformed. Rather than delete the weaker bands — a real loss of market breadth — each opportunity is *labeled* with the tier its evidence supports. + +### The tiers + +- **A — bet this.** `ev_percentage` ≥ 4 (≥ 6 for player props), Pinnacle-anchored, prematch, and the `(book, sport, market)` segment is unflagged by the nightly reliability model. The class with consistently positive graded ROI. +- **B — edge.** `ev_percentage` ≥ 2 (≥ 4 for props) and the segment isn't exclusion-flagged. Includes live, non-Pinnacle-anchored, and watch-flagged rows — historically around break-even. +- **C — informational.** Everything else, plus any exclusion-flagged segment regardless of EV. Surfaced as market intelligence, not a recommended bet. + +### Segment reliability + +A tier is not EV alone. A nightly model scores each `(book, sport, market)` segment against its own graded history and can **cap** an otherwise-strong row: + +- a **watch** flag caps a segment at **B** — its results are shaky but not disqualifying; +- an **exclude** flag forces **C** no matter how large the stated edge, because those cells have historically posted deeply negative ROI. A big EV number there is a red flag, not an opportunity. + +This is why a prematch, Pinnacle-anchored row with a high `ev_percentage` can still land B or C. The bands and flags track the ledger: the reliability table is regenerated on a schedule, so tiers reflect current — not stale — evidence. + +> **Warning:** + A tier reflects the *historical* graded performance of similar opportunities — it is **not** a guarantee for any single bet. Even tier A carries real variance; size with fractional Kelly and never bet more than you can afford to lose. + + ## Variance Warning +EV doesn't guarantee profit on every bet: diff --git a/public/openapi-version.json b/public/openapi-version.json index 1c25d3a..c09dbb3 100644 --- a/public/openapi-version.json +++ b/public/openapi-version.json @@ -1,5 +1,5 @@ { - "version": "3.1.0", + "version": "3.2.0", "x-generated-at": "2026-06-02T23:50:19+00:00", "x-commit-sha": "6667df0" } diff --git a/public/openapi.json b/public/openapi.json index efeb0d2..98d4390 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "SharpAPI", - "version": "3.1.0", + "version": "3.2.0", "description": "Real-time sports betting odds API with +EV detection, arbitrage, middles, and low-hold opportunities.\n\n## Spec Versioning\n\n`info.version` is bumped on every schema or path change. Minor version (`2.x.0`) for additive changes or breaking shape fixes that align the spec to the live response; major version (`x.0.0`) for backward-incompatible redesigns. Removed paths and renamed fields always bump the minor at minimum. Check `x-generated-at` and `x-commit-sha` for the build provenance of a given snapshot.\n\n## Authentication\n\nAll authenticated endpoints accept an API key via one of three methods:\n\n| Method | Header / Param | Use case |\n|--------|---------------|----------|\n| `X-API-Key` | `X-API-Key: sk_live_...` | Recommended for server-side |\n| `Authorization` | `Authorization: Bearer sk_live_...` | Standard Bearer token |\n| `api_key` query | `?api_key=sk_live_...` | SSE/EventSource (cannot set headers) |\n\n## Subscription Tiers\n\n| Tier | Rate Limit | Data Delay | Max Books | EV | Arb | Middles | Game State |\n|------|-----------|------------|-----------|-----|-----|---------|------------|\n| Free | 12/min | 60s | 2 (DK, FD) | - | - | - | - |\n| Hobby | 120/min | Real-time | 5 | - | Yes | - | - |\n| Pro | 300/min | Real-time | 15 | Yes | Yes | Yes | - |\n| Sharp | 1000/min | Real-time | All | Yes | Yes | Yes | - |\n| Enterprise | Custom | Real-time | All | Yes | Yes | Yes | Yes |\n\n## Rate Limit Headers\n\nEvery authenticated response includes:\n\n- `X-RateLimit-Limit` - Requests allowed per minute\n- `X-RateLimit-Remaining` - Requests remaining in current window\n- `X-RateLimit-Reset` - Unix timestamp when the window resets\n- `X-Data-Delay` - Odds delay in seconds for your tier (0 = real-time)\n- `X-Request-Id` - Unique request identifier for support\n\n## WebSocket Streaming\n\nThe WebSocket endpoint at `wss://ws.sharpapi.io` is documented separately in [`asyncapi.yaml`](./asyncapi.yaml) (AsyncAPI 3.0). OpenAPI 3.x cannot express WebSocket subprotocols and message channels, so the SSE endpoint (`/stream`) is the only stream covered by this document.\n\n## MCP Server\n\nThe `POST /mcp` endpoint is a Model Context Protocol server (JSON-RPC 2.0 over Streamable HTTP). Tools are self-described at runtime via `tools/list`, so it's documented as a setup guide rather than an OpenAPI path — see [`/sdks/mcp`](https://docs.sharpapi.io/sdks/mcp).\n", "contact": { "name": "SharpAPI Support", @@ -4757,6 +4757,16 @@ "type": "string" }, "description": "Warning flags (e.g., POTENTIALLY_STALE_ODDS, LIVE_STALE_ODDS, LOW_IMPLIED_TOTAL)" + }, + "quality_tier": { + "type": "string", + "enum": [ + "A", + "B", + "C" + ], + "description": "Grader-certified quality label. A = ev_percentage >= 4 (>= 6 for player props), Pinnacle-anchored (sharp_book = pinnacle), prematch (is_live = false), and the (book, sport, market) segment is unflagged by the nightly reliability model — the only class with consistently positive graded ROI. B = ev_percentage >= 2 (>= 4 props) and the segment is not exclusion-flagged; includes live, non-Pinnacle-anchored, and watch-flagged rows that clear the band. C = everything else, plus any exclusion-flagged segment regardless of EV (disclosed as market intelligence, not a pick). A high ev_percentage alone does not imply A — the sharp anchor, prematch/live state, and the segment-reliability model also gate it. Omitted on suspended rows (is_suspended).", + "example": "A" } } },