Python SDK
Das offizielle sharpapi Python-Paket bietet typisierten Zugriff auf alle SharpAPI-Endpunkte mit Pydantic-Modellen, SSE-Streaming und vollständiger IDE-Autovervollständigung.
Installation
pip install sharpapiMit optionaler pandas-Unterstützung:
pip install sharpapi[pandas]Erfordert Python 3.9+.
Schnellstart
from sharpapi import SharpAPI
client = SharpAPI("sk_live_xxx")
# Arbitrage opportunities
arbs = client.arbitrage.get(min_profit=1.0, league="nba")
for arb in arbs.data:
print(f"{arb.profit_percent:.2f}% profit — {arb.event_name}")
for leg in arb.legs:
print(f" {leg.sportsbook}: {leg.selection} @ {leg.odds_american} ({leg.stake_percent:.1f}%)")
# +EV opportunities
evs = client.ev.get(min_ev=3.0, sport="basketball")
for opp in evs.data:
print(f"+{opp.ev_percentage:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
if opp.kelly_percent:
print(f" Kelly: {opp.kelly_percent:.2f}%, Confidence: {opp.confidence_score}")
# Best odds across sportsbooks
odds = client.odds.best(league="nba", market="moneyline")
for line in odds.data:
print(f"{line.home_team} vs {line.away_team}: {line.selection} {line.odds_american}")Ressourcen
Odds
# Full snapshot with filters
client.odds.get(sport="basketball", league="nba", limit=100)
# Best odds per selection across books
client.odds.best(league="nfl", market="moneyline")
# Side-by-side comparison for an event
client.odds.comparison(event_id="evt_abc123")
# Batch lookup
client.odds.batch(event_ids=["evt_abc123", "evt_def456"])+EV-Gelegenheiten (Pro+)
evs = client.ev.get(
min_ev=2.0,
sportsbook="draftkings",
league="nba",
sort="-ev", # Highest EV first
max_odds_age=60, # Only fresh odds
limit=50,
)
for opp in evs.data:
print(f"+{opp.ev_percentage:.1f}% on {opp.selection} @ {opp.sportsbook}")
print(f" Fair probability: {opp.fair_probability}")
print(f" Devig: {opp.devig_method} via {opp.sharp_book}")
print(f" Kelly: {opp.kelly_percent:.2f}%")
print(f" Confidence: {opp.confidence_score}/100")Arbitrage (Hobby+)
arbs = client.arbitrage.get(
min_profit=0.5,
sport="basketball",
group="best", # One per event+market
max_odds_age=30, # Only fresh odds
)
for arb in arbs.data:
if arb.possibly_stale:
continue # Skip stale opportunities
print(f"{arb.profit_percent:.2f}% — {arb.event_name}")
if arb.game_state:
gs = arb.game_state
print(f" Live: {gs.period} {gs.clock} ({gs.score_home}-{gs.score_away})")
for leg in arb.legs:
print(f" {leg.sportsbook}: {leg.selection} @ {leg.odds_american} → stake {leg.stake_percent:.1f}%")Middles (Pro+)
middles = client.middles.get(sport="football", min_size=3.0, sort="quality")
for mid in middles.data:
print(f"{mid.event_name} — gap: {mid.middle_size} pts")
if mid.side1 and mid.side2:
print(f" {mid.side1.book}: {mid.side1.selection} {mid.side1.line} @ {mid.side1.odds.american}")
print(f" {mid.side2.book}: {mid.side2.selection} +{mid.side2.line} @ {mid.side2.odds.american}")
print(f" Hit probability: {mid.middle_probability:.1%}")
print(f" Expected value: ${mid.expected_value:.2f}")
print(f" Key numbers: {mid.key_numbers}")Low Hold
low_holds = client.low_hold.get(max_hold=2.0, sport="basketball")
for lh in low_holds.data:
print(f"{lh.event_name}: {lh.hold_percentage:.2f}% hold")Referenzdaten
client.sports.list()
client.leagues.list(sport="basketball")
client.sportsbooks.list()
client.events.list(league="nba", live=True)
client.events.search("Lakers")Konto
info = client.account.me()
print(f"Tier: {info.key['tier']}")
print(f"Rate limit: {info.limits.requests_per_minute} req/min")
print(f"Features: EV={info.features.ev}, Arb={info.features.arbitrage}")SSE-Streaming
Echtzeit-Streaming mit Handler-basierten oder Iterator-basierten Mustern.
Wichtig: odds:update-Ereignisse sind Deltas — sie enthalten nur Quoten, die sich geändert haben. Ihr Client muss den lokalen Zustand verwalten und Aktualisierungen in diesen einfügen. Wenn jedes Ereignis als vollständiger Snapshot behandelt wird, entstehen fehlerhafte Daten.
Handler-basiert (Decorator-Muster)
stream = client.stream.opportunities(league="nba", min_ev=3.0)
@stream.on("ev:detected")
def on_ev(data):
for opp in data:
if not opp.get("possibly_stale"):
print(f"+EV: {opp['selection']} {opp['ev_percentage']}% @ {opp['sportsbook']}")
@stream.on("arb:detected")
def on_arb(data):
for arb in data:
print(f"Arb: {arb['profit_percent']}% — {arb['event_name']}")
@stream.on("snapshot:complete")
def on_ready(data):
print(f"Ready: {data['total_odds']} odds from {', '.join(data['books'])}")
stream.connect() # Blocks, processing eventsIterator-basiert
stream = client.stream.all(sport="basketball")
for event_type, data in stream.iter_events():
if event_type == "ev:detected":
for opp in data:
print(f"+EV: {opp['ev_percentage']}%")
elif event_type == "arb:detected":
for arb in data:
print(f"Arb: {arb['profit_percent']}%")
elif event_type == "snapshot:complete":
print("Stream ready")Vollständige Zustandsverwaltung
odds_map = {} # Keyed by odds line ID
stream = client.stream.all(league="nba")
@stream.on("snapshot")
def on_snapshot(data):
for key, value in data.items():
if isinstance(value, list) and key not in ("ev", "arbitrage", "middles", "low_hold"):
for odds in value:
odds_map[odds["id"]] = odds
@stream.on("odds:update")
def on_update(data):
# DELTA — merge into local state
for book, odds_list in data.items():
if isinstance(odds_list, list):
for odds in odds_list:
odds_map[odds["id"]] = odds
@stream.on("odds:removed")
def on_removed(data):
for odds_id in data.get("ids", []):
odds_map.pop(odds_id, None)
@stream.on("connected")
def on_connected(data):
if data.get("reconnected"):
odds_map.clear() # Clear stale state on reconnect
stream.connect()Stream-Kanäle
# Odds only
client.stream.odds(league="nba")
# Opportunities only (EV + arb + middles)
client.stream.opportunities(min_ev=3.0)
# Everything
client.stream.all(sport="basketball")
# Single event
client.stream.event("evt_abc123")Datenqualität
Jede Gelegenheit enthält Metadaten zur Veraltung:
arbs = client.arbitrage.get()
for arb in arbs.data:
# Skip stale or suspicious opportunities
if arb.possibly_stale:
print(f"Stale ({arb.oldest_odds_age_seconds}s old)")
continue
if "LIVE_HIGH_PROFIT_SUSPICIOUS" in arb.warnings:
print("Phantom arb — skipping")
continue
print(f"Actionable: {arb.profit_percent}%")Rate Limits
Informationen zu Rate Limits sind nach jeder Anfrage verfügbar:
response = client.odds.get()
rl = client.rate_limit
print(f"{rl.remaining}/{rl.limit} requests remaining (tier: {rl.tier})")Fehlerbehandlung
from sharpapi import (
SharpAPI,
AuthenticationError,
TierRestrictedError,
RateLimitedError,
)
client = SharpAPI("sk_live_xxx")
try:
evs = client.ev.get()
except AuthenticationError:
print("Invalid API key")
except TierRestrictedError as e:
print(f"Upgrade to {e.required_tier} for this feature")
except RateLimitedError as e:
print(f"Rate limited — retry after {e.retry_after}s")Hilfsfunktionen zur Quotenumrechnung
from sharpapi import american_to_decimal, american_to_probability, decimal_to_american
american_to_decimal(-110) # 1.909
american_to_decimal(150) # 2.5
american_to_probability(-110) # 0.524
decimal_to_american(2.5) # 150Context Manager
with SharpAPI("sk_live_xxx") as client:
arbs = client.arbitrage.get()
# HTTP client automatically closed on exitTypsicherheit
Alle Antworten verwenden Pydantic-Modelle mit vollständigen Typhinweisen:
from sharpapi import EVOpportunity, ArbitrageOpportunity
# IDE autocomplete works on all fields
arbs = client.arbitrage.get()
arb: ArbitrageOpportunity = arbs.data[0]
arb.profit_percent # float
arb.legs[0].sportsbook # str
arb.game_state.period # str | None