# AltSportsData SDK

[![PyPI](https://img.shields.io/pypi/v/altsportsdata.svg)](https://pypi.org/project/altsportsdata/)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)

**Production-grade alternative sports data feed — odds, events, futures, settlement for 30 leagues.**

Original model-generated probabilities for sports nobody else can price.
Built for prediction markets, DFS platforms, and sportsbooks.

```
pip install altsportsdata
```

---

## Quick Start

```python
from altsportsdata import AltSportsData

client = AltSportsData(api_key="your_key")

# Discover what's available — every response is a DataFrame
leagues = client.get_leagues()
print(leagues)
```

```
league      name                                data_shape  archetype         market_count  markets
──────────  ──────────────────────────────────  ──────────  ────────────────  ────────────  ────────────────────────
spr         Supercross                          field       racing            6             matchup, moneyline, ...
wsl         World Surf League                   bracket     heat_elimination  6             heat_winner, matchup...
bkfc        Bare Knuckle Fighting Championship  match       combat            1             heat_winner
f1          Formula 1                           field       racing            9             matchup, moneyline, ...
```

```python
# Scope to a league — instant self-documenting discovery
spr = client.get_league("spr")
spr.data_shape       # → "field"  (large field, position markets)
spr.archetype        # → "racing"
spr.markets          # → ["matchup", "moneyline", "podium", "show", ...]
spr.describe()       # → pretty ASCII market menu with method names

# Get odds — straight to DataFrame
events = spr.get_events(status="upcoming")
odds = spr.get_moneylines(events[0].id)
print(odds)          # clean table
odds.df              # pandas DataFrame
```

---

## For Prediction Markets (Kalshi, Polymarket)

Fair probabilities, vig-free, summing to 1.0. Ready for contract creation.

```python
client = AltSportsData(api_key="your_key", odds_format="probability")
spr = client.get_league("spr")

# What kind of contracts should I create?
spr.data_shape          # → "field" — many binary contracts per event
hints = spr.contract_hints
hints["contract_types"]     # → ["outright_winner", "top_n_finish", "exacta", "head_to_head"]
hints["probability_shape"]  # → "long-tail (favorite ~15-40%, field ~1-10% each)"
hints["settlement"]         # → "Position-based — clear winner, top-N verifiable"
```

### Fair Probabilities (vig-removed)

Raw API odds include bookmaker margin. `get_fair_probabilities()` removes the
vig so probabilities sum to exactly **1.0** — ready for contract pricing.

```python
probs = spr.get_fair_probabilities(event_id)
print(probs)
```

```
Fair Probabilities (margin=61.0%)

athlete             fair_probability  raw_odds  margin  outcome_id
──────────────────  ────────────────  ────────  ──────  ──────────────────────────────────
Eli Tomac           0.3069            2.02      61      7247a2d1-6a0e-4ef1-...
Hunter Lawrence     0.2333            2.66      61      69a9aa04-3ea4-416d-...
Ken Roczen          0.1459            4.26      61      d31567d2-65da-48aa-...
Cooper Webb         0.0908            6.84      61      682ab924-2572-46ac-...
```

```python
# Probabilities sum to exactly 1.0
total = sum(p["fair_probability"] for p in probs)
# → 1.000000

# Create contracts from the data
for p in probs:
    create_contract(
        question=f"Will {p['athlete']} win Indianapolis Supercross?",
        probability=p["fair_probability"],
        settlement_id=p["outcome_id"],
    )
```

### Fair Matchup Probabilities

Head-to-head matchups devigged per pair — each pair sums to 1.0:

```python
matchups = spr.get_fair_matchup_probabilities(event_id)
print(matchups)
```

```
Fair Matchup Probabilities

player1           fair_prob1  player2          fair_prob2  margin
────────────────  ──────────  ───────────────  ──────────  ──────
Cooper Webb       0.4046      Ken Roczen       0.5954      6.98
Eli Tomac         0.5620      Hunter Lawrence  0.4380      7.19
```

### Data Shapes — Know What Contracts to Create

Every league has a **data shape** that tells you the contract structure:

| Shape | Leagues | Contract Pattern |
|-------|---------|-----------------|
| `field` | F1, Supercross, NHRA, Disc Golf... (12) | Many binary: "Will X win?", "Top 3?", "Exacta?" |
| `match` | BKFC, MASL, Power Slap, NLL... (12) | One binary per matchup: "Will A beat B?" |
| `bracket` | WSL, SLS, Formula Drift (3) | Layered: heat-level + event-level contracts |

```python
f1 = client.get_league("f1")
f1.data_shape       # → "field"
f1.contract_hints   # → full contract guidance

bkfc = client.get_league("bkfc")
bkfc.data_shape     # → "match"

wsl = client.get_league("wsl")
wsl.data_shape      # → "bracket"
```

### Settlement for Contract Resolution

```python
result = client.get_settlement(event_id)
for name, mkt in result["markets"].items():
    for o in mkt.get("winners", []):
        print(f"  ✅ {o['athlete']} — settled at {result['settled_at']}")
```

---

## For DFS Platforms (PrizePicks, Underdog)

```python
client = AltSportsData(api_key="your_key")

# Head-to-head matchups — ready for pick'em
matchups = client.get_matchups(event_id)
for m in matchups:
    print(f"{m.player1} ({m.odds1:.2f})  vs  {m.player2} ({m.odds2:.2f})")
```

```
Cooper Webb (2.31)  vs  Ken Roczen (1.57)
Eli Tomac (1.66)  vs  Hunter Lawrence (2.13)
```

```python
# Player props + totals
props = client.get_player_props(event_id)
totals = client.get_player_totals(event_id, stat="finishingPosition")

# Everything is a DataFrame
matchups.df.to_csv("matchups.csv")
```

---

## For Sportsbooks (DraftKings, Bet365, Stake)

```python
# American odds
client = AltSportsData(api_key="your_key", odds_format="american")
odds = client.get_moneylines(event_id)
print(odds)

# Fractional odds (UK books)
client = AltSportsData(api_key="your_key", odds_format="fractional")
```

---

## Market-First Discovery

Sportsbooks think market-first: "Give me all matchups" — not "give me WSL, then check matchups."

```python
# All head-to-head matchups across every league
client.get_markets(market="matchup")

# All moneylines in racing sports
client.get_markets(market="moneyline", archetype="racing")

# All upcoming markets for one league
wsl = client.get_league("wsl")
wsl.get_markets()
```

---

## Odds Format Conversion

Set once on the client — all responses auto-convert:

```python
client = AltSportsData(api_key="key", odds_format="probability")  # Kalshi
client = AltSportsData(api_key="key", odds_format="american")     # DraftKings
client = AltSportsData(api_key="key", odds_format="decimal")      # Stake (default)
client = AltSportsData(api_key="key", odds_format="fractional")   # Bet365
```

Or convert individual values:

```python
from altsportsdata import convert_odds

convert_odds(2.50, "decimal", "american")     # → 150.0
convert_odds(2.50, "decimal", "probability")  # → 0.4
convert_odds(150, "american", "decimal")      # → 2.5
convert_odds("3/2", "fractional", "probability")  # → 0.4
```

---

## Async Client (Production Infrastructure)

```python
from altsportsdata import AsyncAltSportsData
import asyncio

async def main():
    async with AsyncAltSportsData(api_key="key", odds_format="probability") as client:
        wsl = client.get_league("wsl")
        events = await wsl.get_events(status="upcoming")

        # Batch fetch — all events concurrently
        ids = [e.id for e in events[:20]]
        batch = await client.get_odds_batch(ids, "moneyline")
        for eid, odds in batch.items():
            if "error" not in odds:
                print(f"{eid}: {len(odds.get('eventWinner', []))} outcomes")

asyncio.run(main())
```

Requires: `pip install altsportsdata[async]`

---

## Enterprise Reliability

Built for production — automatic retry, rate limit handling, request tracing.

```python
client = AltSportsData(
    api_key="key",
    max_retries=3,        # exponential backoff on 429, 5xx
    retry_backoff=0.5,    # base delay in seconds
    timeout=30,           # per-request timeout
)
```

- Automatic exponential backoff with jitter on 429/5xx
- Respects `Retry-After` headers
- Request IDs (`X-Request-ID`) for debugging
- Thread-safe session management
- Context manager support: `with AltSportsData(...) as client:`

---

## Full API Reference

### Setup

```python
from altsportsdata import AltSportsData

# General client
client = AltSportsData(api_key="your_key")

# League-scoped — auto-filters everything
wsl = client.get_league("wsl")
f1  = client.get_league("f1")
spr = client.get_league("spr")

# With options
client = AltSportsData(
    api_key="your_key",
    league="wsl",
    odds_format="probability",
    max_retries=3,
)
```

### Discovery

```python
# All leagues with data shapes and market menus
leagues = client.get_leagues()
print(leagues)        # clean table
df = leagues.df       # pandas DataFrame

# Filter by archetype or market support
client.get_leagues(archetype="racing")
client.get_leagues(market="matchup")
client.get_leagues(archetype="racing", market="exacta")

# League details
info = client.get_league_info("f1")
info["data_shape"]      # → "field"
info["contract_hints"]  # → {contract_types, probability_shape, ...}
info["markets"]         # → ["moneyline", "matchup", ...]

# Self-documenting league card
spr = client.get_league("spr")
spr.describe()          # pretty-prints market menu with methods
spr.data_shape          # → "field"
spr.contract_hints      # → contract creation guidance

# All market types
client.list_market_types()

# Market catalog with event counts
for lg in client.get_market_catalog():
    print(f"{lg['league']:12} upcoming={lg['upcoming_events']}")
```

### Events

```python
events = client.get_events(status="upcoming")        # ResultSet with .df
events = client.get_events(status="live")
events = client.get_events(status="completed")
events = client.get_events(status=["live", "upcoming"])

print(events)        # clean table
df = events.df       # pandas DataFrame
events.to_csv("events.csv")

event = client.get_event("event_id")
participants = client.get_participants("event_id")
```

### Markets (cross-league, prices included)

```python
# Market-first — filter by market type across all leagues
markets = client.get_markets(market="matchup")              # all H2H matchups
markets = client.get_markets(market="moneyline", archetype="racing")

# League-scoped
wsl = client.get_league("wsl")
markets = wsl.get_markets()                                 # all market types
markets = wsl.get_markets(status="live")                    # live only

print(markets)       # clean table
df = markets.df      # pandas DataFrame
```

### Odds (per event) — all return `OddsResult` with `.df`

```python
# Sportsbook
odds = client.get_moneylines("event_id")    # event winner
odds = client.get_matchups("event_id")      # head-to-head
odds = client.get_totals("event_id")        # over/under
odds = client.get_exactas("event_id")       # exacta
odds = client.get_podiums("event_id")       # top-3
odds = client.get_heat_winners("event_id")  # heat winner
odds = client.get_fastest_lap("event_id")   # fastest lap

# Prediction market — fair probabilities
probs = client.get_fair_probabilities("event_id")        # vig-removed, sums to 1.0
probs = client.get_fair_matchup_probabilities("event_id") # per-matchup devig
probs = client.get_market_probabilities("event_id")      # raw probs (with vig)
probs = client.get_podium_probabilities("event_id")
probs = client.get_top_finish_probabilities("event_id", top_n=5)

# DFS
odds = client.get_player_props("event_id")
odds = client.get_player_matchups("event_id")
odds = client.get_player_totals("event_id", stat="points")

# Generic — any market by name or alias
odds = client.get_odds("event_id", "moneyline")

# Every OddsResult has .df
print(odds)          # clean table
df = odds.df         # pandas DataFrame
odds.to_csv("odds.csv")
```

### Batch Operations

```python
events = client.get_events(league="spr", status="upcoming")
ids = [e.id for e in events]
batch = client.get_odds_batch(ids, "moneyline", max_concurrent=5)
```

### Settlement

```python
result = client.get_settlement("event_id")
# → {event_id, event_name, league, status, settled_at,
#    markets: {eventWinner: {outcomes, winners}, headToHead: {...}}}
```

### Live Polling

```python
for update in client.poll_odds(event_id, "moneyline", interval=5, max_polls=60):
    print(update)
```

### Futures

```python
client.list_futures()
client.get_futures(tour="tour_id", type="winner")
```

### Odds Conversion (Static)

```python
AltSportsData.convert(2.50, "decimal", "american")     # → 150.0
AltSportsData.convert(2.50, "decimal", "probability")  # → 0.4
```

---

## 30 Leagues × 3 Data Shapes

### Field (large competitor pools)

| Code | League | Markets |
|------|--------|---------|
| `f1` | Formula 1 | moneyline, matchup, top_3, top_5, top_10, exacta, over_under, trifecta |
| `spr` | Supercross | moneyline, matchup, podium, show, over_under |
| `motocrs` | Pro Motocross | moneyline, matchup, podium, show, over_under, heat_winner |
| `nhra` | NHRA Drag Racing | moneyline, matchup, heat_winner, over_under |
| `dgpt` | Disc Golf Pro Tour | moneyline, matchup, top_3, top_5, over_under, heat_winner |
| `nrx` | Nitrocross | moneyline, matchup, podium, show, over_under, heat_winner |
| `worldoutlaws` | World of Outlaws | moneyline, matchup, heat_winner, over_under |
| `hlrs` | High Limit Racing | moneyline, matchup, heat_winner, over_under |
| `usac` | USAC Racing | moneyline, matchup, heat_winner, over_under |
| `motoamerica` | MotoAmerica | moneyline, matchup, over_under |
| `sprmtcrs` | Supermotocross | moneyline, matchup, podium, heat_winner, over_under |
| `xgame` | X Games | moneyline, matchup, heat_winner, over_under |

### Match (two-sided contests)

| Code | League | Markets |
|------|--------|---------|
| `bkfc` | Bare Knuckle FC | heat_winner |
| `powerslap` | Power Slap | heat_winner |
| `masl` | Major Arena Soccer | moneyline |
| `nll` | National Lacrosse League | — |
| `jaialai` | Jai Alai | moneyline |
| `byb` | BYB Extreme Fighting | heat_winner |
| `lux` | LUX Fight League | heat_winner |
| `raf` | Real American Freestyle | heat_winner |
| `athletesunlimited` | Athletes Unlimited | moneyline, matchup |
| `gsoc` | Global Soccer | — |
| `mltt` | Major League Table Tennis | — |
| `spectation` | Spectation | heat_winner |

### Bracket (heat elimination)

| Code | League | Markets |
|------|--------|---------|
| `wsl` | World Surf League | moneyline, matchup, heat_winner, podium, props, show |
| `sls` | Street League Skateboarding | moneyline, matchup, heat_winner, podium, props, show |
| `fdrift` | Formula Drift | moneyline, matchup, heat_winner |

---

## Links

- [API Docs](https://api.altsportsdata.com/public/docs)
- [Swagger](https://api.altsportsdata.com/public/docs/swagger)

## License

MIT
