Skip to Content
StreamingÜbersicht

Streaming-Übersicht

Echtzeit-Aktualisierungen von Quoten und Opportunitäten über SSE oder WebSocket.

SharpAPI bietet zwei Streaming-Protokolle: SSE (Server-Sent Events) über HTTP und WebSocket für bidirektionale Kommunikation. Beide liefern dieselben Echtzeitdaten — wählen Sie basierend auf Ihrem Anwendungsfall.

Warum Streaming?Permalink for this section

Mit REST sehen Sie eine Änderung erst bei Ihrer nächsten Abfrage. Streaming sendet Ihnen geänderte Zeilen, sobald sie auftreten.

AnsatzUpdate-ZustellungBandbreiteAnwendungsfall
REST-PollingBei Ihrer nächsten AbfrageHoch (volle Nutzlast bei jeder Abfrage)Gelegentliches Browsen, Dashboards
SSE-StreamingPush bei ÄnderungNiedrig (nur Deltas)Live-Wetten, Benachrichtigungen, automatisierte Systeme
WebSocketPush bei ÄnderungNiedrig (nur Deltas)Bidirektionale Kommunikation, dynamische Filter-Updates

Wesentliche VorteilePermalink for this section

  • Kein Abfrageintervall — Updates werden gesendet, sobald unsere Pipeline eine Änderung erkennt, statt auf Ihre nächste Anfrage zu warten
  • Geringere Bandbreite — Es werden nur geänderte Daten gesendet, keine vollständigen Snapshots bei jeder Abfrage
  • Zwei Protokolle — SSE für Einfachheit, WebSocket für bidirektionale Kontrolle
  • Automatische Wiederverbindung — SSE verbindet sich nativ wieder; WebSocket mit einfacher Wiederholungslogik

Wie SSE funktioniertPermalink for this section

Server-Sent Events ist ein W3C-Standard für Server-zu-Client-Streaming über HTTP:

Client Server | | |--- GET /api/v1/stream ------->| | | |<-- event: connected ----------| Stream established |<-- event: snapshot -----------| Full current state |<-- event: odds:update --------| Delta update |<-- event: odds:update --------| Delta update |<-- event: ev:detected --------| Opportunity (new or updated) |<-- event: heartbeat ----------| Keep-alive (every 30s) | ... |

SSE verbindet sich bei einer Trennung automatisch wieder. Der Server verwendet Last-Event-ID, um verpasste Ereignisse erneut abzuspielen.

AnforderungenPermalink for this section

TarifStreaming-Zugriff
FreeNicht verfügbar
Hobby + Add-on ($99/Monat)1 Stream (Newer-Wins-Verdrängung)
Pro + Add-on ($99/Monat)1 Stream (Newer-Wins-Verdrängung)
Sharp + Add-on ($99/Monat)1 Stream (Newer-Wins-Verdrängung)
EnterpriseInklusive (individuelle Limits)

Newer-Wins-Verdrängung: Das Öffnen eines zweiten Streams mit demselben API-Schlüssel schließt den ersten. Eine gut verwaltete Verbindung reicht für die meisten Anwendungsfälle aus — siehe Single-Connection-Patterns für Techniken. Flotten-Deployments, die mehrere gleichzeitige Streams benötigen, können über den Vertrieb ein höheres Limit anfordern.

SchnellstartPermalink for this section

BrowserPermalink for this section

// Local odds map — snapshot fills it, deltas merge into it const oddsMap = new Map(); const eventSource = new EventSource( 'https://api.sharpapi.io/api/v1/stream?channel=all&league=nba&api_key=YOUR_KEY' ); eventSource.addEventListener('connected', (e) => { const { stream_id, resumed } = JSON.parse(e.data); // A full snapshot follows every connect except a resume (resumed === true, // `odds` channel only), which replays missed deltas into the state you hold. // A started resume can still fall back to a full snapshot; this short example // leaves that out — the Streaming Reliability Contract has the complete client. if (resumed !== true) oddsMap.clear(); console.log('Stream connected:', stream_id); }); eventSource.addEventListener('snapshot', (e) => { const { odds } = JSON.parse(e.data); if (!odds) return; // opportunity chunks carry `ev` / `arbitrage` / `middles` / `low_hold` instead for (const odd of odds) oddsMap.set(odd.id, odd); console.log('Snapshot chunk:', odds.length, 'odds'); }); eventSource.addEventListener('odds:update', (e) => { const { odds, book } = JSON.parse(e.data); // Delta only contains dynamic fields — merge into local state by ID for (const delta of odds) { const full = oddsMap.get(delta.id); if (full) Object.assign(full, delta); else oddsMap.set(delta.id, delta); } console.log(`${book}: ${odds.length} odds updated`); }); eventSource.addEventListener('ev:detected', (e) => { // {opportunities, count, type}: new opportunities, or updated versions of ones already sent (same `id`) const { opportunities: opps } = JSON.parse(e.data); opps.forEach(opp => console.log(`+EV: ${opp.selection} at ${opp.ev_percentage}% EV`)); }); eventSource.addEventListener('heartbeat', () => { console.log('Connection alive'); }); eventSource.onerror = () => { console.log('Connection lost, auto-reconnecting...'); };

Node.jsPermalink for this section

import EventSource from 'eventsource'; const oddsMap = new Map(); const es = new EventSource( 'https://api.sharpapi.io/api/v1/stream?channel=odds&league=nba', { headers: { 'X-API-Key': 'YOUR_KEY' } } ); es.addEventListener('snapshot', (e) => { const { odds } = JSON.parse(e.data); for (const odd of odds) oddsMap.set(odd.id, odd); console.log(`Received ${odds.length} initial odds`); }); es.addEventListener('connected', (e) => { const { resumed } = JSON.parse(e.data); if (resumed !== true) oddsMap.clear(); }); es.addEventListener('odds:update', (e) => { const { odds, book } = JSON.parse(e.data); // Merge compact deltas into local state for (const delta of odds) { const full = oddsMap.get(delta.id); if (full) Object.assign(full, delta); else oddsMap.set(delta.id, delta); } console.log(`${book}: ${odds.length} odds updated`); });

PythonPermalink for this section

import sseclient import requests import json url = 'https://api.sharpapi.io/api/v1/stream' params = {'channel': 'all', 'league': 'nba'} headers = {'X-API-Key': 'YOUR_KEY'} odds_map: dict[str, dict] = {} response = requests.get(url, params=params, headers=headers, stream=True) client = sseclient.SSEClient(response) for event in client.events(): data = json.loads(event.data) if event.data else {} if event.event == 'connected': # A full snapshot follows every connect except a resume (resumed == True, # odds channel only), which replays deltas into your existing state. # A started resume can still fall back to a full snapshot; this short # example leaves that out — see the Streaming Reliability Contract. if data.get('resumed') is not True: odds_map.clear() print(f"Stream {data['stream_id']} connected") elif event.event == 'snapshot': for odd in data.get('odds', []): odds_map[odd['id']] = odd print(f"Snapshot: {data['count']} odds") elif event.event == 'odds:update': # Delta only has dynamic fields — merge by ID for delta in data.get('odds', []): existing = odds_map.get(delta['id']) if existing: existing.update(delta) else: odds_map[delta['id']] = delta print(f"{data['book']}: {data['count']} odds updated") elif event.event == 'ev:detected': # {opportunities, count, type}: new, or updated versions of ones already sent (same id) for opp in data['opportunities']: print(f"+EV: {opp['selection']} at {opp['ev_percentage']}%")

EreignistypenPermalink for this section

EreignisBeschreibung
connectedStream aufgebaut, gibt Stream-ID, aktive Filter und Kanäle zurück
snapshot / opportunities_snapshotVollständiger Daten-Dump für Quoten/Opportunitäten (alle Felder)
snapshot:completeAlle initialen Daten wurden gesendet
odds:updateKompaktes Delta — nur dynamische Felder (id, odds_american, odds_decimal, odds_probability, line, is_live, timestamp). Per id in den Snapshot-Zustand einfügen. Eine Zeile mit einer id, die noch nicht in Ihrer Map vorhanden ist, wird vollständig übermittelt — speichern Sie sie.
odds:removedQuoten von einem Sportsbook entfernt
ev:detectedNeue +EV-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id) — Upsert per id
ev:expired+EV-Opportunität nicht mehr verfügbar
arb:detectedNeue Arbitrage-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id)
arb:expiredArbitrage-Opportunität nicht mehr verfügbar
middles:detectedNeue Middle-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id). Siehe Middles-Zusammenfassung für aggregierte Statistiken
middles:expiredMiddle-Opportunität nicht mehr verfügbar
low_hold:detectedNeue Low-Hold-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id)
low_hold:expiredLow-Hold-Opportunität nicht mehr verfügbar
heartbeatKeep-Alive wird alle 30 Sekunden gesendet
errorBehebbarer Fehler (Verbindung bleibt offen)

SSE vs WebSocketPermalink for this section

FunktionSSEWebSocket
ProtokollHTTP (einseitig)ws:// / wss:// (bidirektional)
EndpointGET /api/v1/streamwss://ws.sharpapi.io
Kanal-AbonnementsEinmal über Query-Parameter festgelegtJederzeit per subscribe-Nachricht aktualisierbar
Filter-UpdatesMit neuen Parametern wieder verbindensubscribe-Nachricht senden
WiederverbindungAutomatisch (Last-Event-ID)Manuell (mit Backoff)
Browser-UnterstützungNatives EventSourceNatives WebSocket
Am besten fürEinfache Konsumenten, SSRDynamische Filter, Zwei-Wege-Kommunikation

Beide Protokolle liefern dieselben Ereignistypen und Daten-Nutzlasten.

Vollständige API-ReferenzPermalink for this section

SDKs mit Streaming-UnterstützungPermalink for this section

  • TypeScript SDK — Eingebauter SSE-Client mit typsicheren Event-Handlern
  • Python SDK — Handler-basierte und Iterator-basierte Streaming-Muster

BeispielprojektePermalink for this section

Last updated on