σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1,2 Tsd. Lernende
AnmeldenBeitreten ↗
Skills
AO
+1,2 Tsd. Lernende
Anmelden
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python-Skill›Modul 9 · Marktdaten und Mikrostruktur›Thema 9.4·~55 Min.Fortgeschritten

Datenstreaming mit Websockets

Position in der SkillModul 9 von 11 · Thema 4 von 5
9.4 Datenstreaming mit Websockets
—Lab-Punkte— / 100XP gesamt—Serie—

Eine Verbindung, die offen bleibt

Eine normale Webanfrage ist eine Frage und eine Antwort, dann schließt die Verbindung. Für Live-Marktdaten hieße das, immer wieder zu fragen: langsam und stets etwas hinterher. Ein Websocket beginnt als gewöhnliche HTTP-Anfrage und wechselt dann in eine Verbindung, die in beide Richtungen offen bleibt, sodass der Server jeden Handel im Moment des Geschehens senden kann. Börsen und Broker bieten ihre Live-Feeds so an: Man verbindet sich, sendet eine subscribe-Nachricht mit dem Gewünschten und erhält einen Strom kleiner JSON-Nachrichten.

# A subscribe message and a trade message, both small JSON texts
import json

subscribe = json.dumps({"op": "subscribe", "channel": "trades", "symbols": ["AAPL", "MSFT"]})
print(subscribe)                                          # {"op": "subscribe", "channel": "trades", "symbols": ["AAPL", "MSFT"]}

raw = '{"s": "AAPL", "p": "184.25", "q": 100, "t": 1700000000123}'
message = json.loads(raw)
print(message["s"], float(message["p"]), message["q"])    # AAPL 184.25 100

Nachrichten sicher lesen

Alles auf der Leitung ist Text, und dieser Text steht nicht unter Ihrer Kontrolle. Dekodieren Sie ihn mit json.loads, nie mit eval, das ausführen würde, was ein Absender geschrieben hat. Rechnen Sie mit Überraschungen: Preise kommen oft als Strings, um Float-Rundung auf dem Server zu vermeiden, und nicht jede Nachricht ist ein Handel. Feeds senden alle paar Sekunden Heartbeats, damit man einen ruhigen Markt von einer toten Verbindung unterscheiden kann; erkennen Sie sie und überspringen Sie sie, und werten Sie langes Schweigen als Zeichen zum Neuverbinden.

# One function turns a raw message into a trade, or None for a heartbeat
import json

def parse_tick(raw):
    message = json.loads(raw)
    if message.get("type") == "hb":
        return None
    return {"symbol": message["s"], "price": float(message["p"]), "size": int(message["q"]), "ts": int(message["t"])}

print(parse_tick('{"s": "AAPL", "p": "184.25", "q": 100, "t": 1700000000123}'))
print(parse_tick('{"s": "MSFT", "p": 410.5, "q": 25, "t": 1700000000456}'))
print(parse_tick('{"type": "hb", "t": 1700000001000}'))      # None  a heartbeat carries no trade

Verlorene Nachrichten und Neuverbinden

Verbindungen brechen ab, deshalb nummeriert ein Feed seine Nachrichten. Kommt 7 und dann 11, sind die Nachrichten 8, 9 und 10 verloren und Ihre Daten haben ein Loch: erkennen Sie es und reparieren Sie es, etwa durch Anfordern eines Snapshots. Nach einem Abbruch verbindet man sich neu, aber nicht sofort und nicht im selben Moment wie alle anderen Clients: Warten Sie base, dann doppelt so lang, dann wieder doppelt, bis zu einer Obergrenze. Dieses exponentielle Backoff verhindert, dass ein angeschlagener Server überrannt wird. Im echten Code ist die Empfangsschleife ein async for message in connection: im zuletzt gesehenen asyncio.

# Gaps in the sequence numbers show the messages that were lost
def find_gaps(seqs):
    gaps = []
    for previous, current in zip(seqs, seqs[1:]):
        if current != previous + 1:
            gaps.append((previous + 1, current - 1))
    return gaps

print(find_gaps([1, 2, 3, 4]))             # []                 nothing lost
print(find_gaps([1, 2, 3, 7, 8, 11]))      # [(4, 6), (9, 10)]  4-6 and 9-10 are missing
# Waiting twice as long before each reconnect, up to a cap
def backoff_delays(attempts, base, cap):
    return [min(base * 2 ** i, cap) for i in range(attempts)]

print(backoff_delays(6, 0.5, 8.0))         # [0.5, 1.0, 2.0, 4.0, 8.0, 8.0]
print(backoff_delays(3, 1.0, 30.0))        # [1.0, 2.0, 4.0]
# A feed consumed with async for; the websockets package gives a connection used the same way
import asyncio

class FakeConnection:
    """Stands in for a websocket: an async iterator of raw text messages."""
    def __init__(self, messages):
        self.messages = list(messages)
    def __aiter__(self):
        return self
    async def __anext__(self):
        if not self.messages:
            raise StopAsyncIteration
        await asyncio.sleep(0)
        return self.messages.pop(0)

async def consume(connection):
    trades = []
    async for raw in connection:
        if '"hb"' not in raw:
            trades.append(raw)
    return trades

feed = FakeConnection(['{"s": "AAPL", "p": 184.25}', '{"type": "hb"}', '{"s": "MSFT", "p": 410.5}'])
print(asyncio.run(consume(feed)))          # the two trades; the heartbeat is skipped
Was das Lab nicht brauchtDas Browser-Lab hat kein Netz, Sie üben also die Logik um einen Feed, wo die Fehler sitzen. Für den Live-Betrieb installieren Sie das Paket websockets, rufen await websockets.connect(url) auf, senden Ihre subscribe-Nachricht und starten die async for-Schleife. Legen Sie nie API-Schlüssel in geteilten Code, und rechnen Sie damit, dieselbe Logik für jeden Handelsplatz zu brauchen, jeweils mit eigenem Nachrichtenformat.
  • Ein Websocket ist eine in beide Richtungen offene Verbindung: einmal abonnieren, dann einen Nachrichtenstrom empfangen.
  • Mit json.loads dekodieren, nie eval; Preise als Strings erwarten und Heartbeats überspringen.
  • Sequenznummern verraten verlorene Nachrichten; nach einem Abbruch mit sich verdoppelnder Wartezeit bis zur Obergrenze neu verbinden.

Als Nächstes: das FIX-Protokoll, das Nachrichtenformat, mit dem Broker und Börsen Orders aufgeben.

Lab: Was bewertet wird

  • Mit subscribe_message einen Feed anfordern25 Pkt.
  • Mit parse_tick eine Nachricht lesen25 Pkt.
  • Mit find_gaps verlorene Nachrichten finden25 Pkt.
  • Mit backoff_delays Wiederverbindungen planen25 Pkt.
Explorer · lab-115-1
▾ lab-115-1
…
bash · im Browser
learner@quantcraft:~/lab-115-1$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-115-1 wird auf die Startdateien zurückgesetzt und das Terminal geleert. Ihre aktuelle Lösung geht verloren – das lässt sich nicht rückgängig machen.

Wird zurückgesetzt
  • main.py → Startcode
  • Terminal-Verlauf und Testausgabe
  • Nicht eingereichte Änderungen
Bleibt erhalten
  • Beste Lab-Punktzahl
  • Ihre XP und Ihre Serie
  • Vorlesungsfortschritt und Notizen

Melde dich an, um deinen Code auszuführen.

Alle Lektionen kannst du kostenlos lesen. Erstelle ein Konto oder melde dich an, um Code auszuführen, Tests zu starten und dieses Lab abzugeben.

  • IDE im Browser mit Bash-Terminal — nichts zu installieren
  • Sofortige Bewertung, XP und dein Platz auf der Skill
Konto erstellenAnmelden
ODER
Weiter mit GitHub