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?
Mit REST sehen Sie eine Änderung erst bei Ihrer nächsten Abfrage. Streaming sendet Ihnen geänderte Zeilen, sobald sie auftreten.
| Ansatz | Update-Zustellung | Bandbreite | Anwendungsfall |
|---|---|---|---|
| REST-Polling | Bei Ihrer nächsten Abfrage | Hoch (volle Nutzlast bei jeder Abfrage) | Gelegentliches Browsen, Dashboards |
| SSE-Streaming | Push bei Änderung | Niedrig (nur Deltas) | Live-Wetten, Benachrichtigungen, automatisierte Systeme |
| WebSocket | Push bei Änderung | Niedrig (nur Deltas) | Bidirektionale Kommunikation, dynamische Filter-Updates |
Wesentliche Vorteile
- 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 funktioniert
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.
Anforderungen
| Tarif | Streaming-Zugriff |
|---|---|
| Free | Nicht 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) |
| Enterprise | Inklusive (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.
Schnellstart
Browser
// 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.js
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`);
});Python
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']}%")Ereignistypen
| Ereignis | Beschreibung |
|---|---|
connected | Stream aufgebaut, gibt Stream-ID, aktive Filter und Kanäle zurück |
snapshot / opportunities_snapshot | Vollständiger Daten-Dump für Quoten/Opportunitäten (alle Felder) |
snapshot:complete | Alle initialen Daten wurden gesendet |
odds:update | Kompaktes 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:removed | Quoten von einem Sportsbook entfernt |
ev:detected | Neue +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:detected | Neue Arbitrage-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id) |
arb:expired | Arbitrage-Opportunität nicht mehr verfügbar |
middles:detected | Neue Middle-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id). Siehe Middles-Zusammenfassung für aggregierte Statistiken |
middles:expired | Middle-Opportunität nicht mehr verfügbar |
low_hold:detected | Neue Low-Hold-Opportunität oder eine aktualisierte Version einer bereits gesendeten (gleiche id) |
low_hold:expired | Low-Hold-Opportunität nicht mehr verfügbar |
heartbeat | Keep-Alive wird alle 30 Sekunden gesendet |
error | Behebbarer Fehler (Verbindung bleibt offen) |
SSE vs WebSocket
| Funktion | SSE | WebSocket |
|---|---|---|
| Protokoll | HTTP (einseitig) | ws:// / wss:// (bidirektional) |
| Endpoint | GET /api/v1/stream | wss://ws.sharpapi.io |
| Kanal-Abonnements | Einmal über Query-Parameter festgelegt | Jederzeit per subscribe-Nachricht aktualisierbar |
| Filter-Updates | Mit neuen Parametern wieder verbinden | subscribe-Nachricht senden |
| Wiederverbindung | Automatisch (Last-Event-ID) | Manuell (mit Backoff) |
| Browser-Unterstützung | Natives EventSource | Natives WebSocket |
| Am besten für | Einfache Konsumenten, SSR | Dynamische Filter, Zwei-Wege-Kommunikation |
Beide Protokolle liefern dieselben Ereignistypen und Daten-Nutzlasten.
Vollständige API-Referenz
- SSE Stream API-Referenz — Query-Parameter, Event-Nutzlasten, Wiederverbindung
- WebSocket API-Referenz — Verbindungslebenszyklus, Nachrichtenprotokoll, Close-Codes
- WebSocket Streaming-Anleitung — Schnellstart, Wiederverbindungsmuster, Best Practices
SDKs mit Streaming-Unterstützung
- TypeScript SDK — Eingebauter SSE-Client mit typsicheren Event-Handlern
- Python SDK — Handler-basierte und Iterator-basierte Streaming-Muster
Beispielprojekte
- Arbitrage-Scanner — Echtzeit-Arb-Erkennung mit Discord-Benachrichtigungen
- Value-Betting-Bot — +EV-Benachrichtigungs-Bot für Discord/Telegram