σμ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 Academyالمسارات
Ctrl K
AO
+1.2k متعلّم
تسجيل الدخولانضم ↗
المسارات
AO
+1.2k متعلّم
تسجيل الدخول
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
مسار Python›الوحدة 9 · بيانات السوق وبنيته الدقيقة›الموضوع 9.4·~55 دمتقدم

بث البيانات عبر websockets

الموضع في المسارالوحدة 9 من 11 · الموضوع 4 من 5
9.4 بث البيانات عبر websockets
—درجة المختبر— / 100إجمالي XP—التتابع—

اتصال يبقى مفتوحًا

الطلب العادي على الويب سؤال وجواب ثم يُغلق الاتصال. وللبيانات الحية كان ذلك يعني السؤال مرارًا: بطيء ومتأخر دائمًا قليلًا. أما websocket فيبدأ كطلب HTTP عادي ثم يترقّى إلى اتصال يبقى مفتوحًا في الاتجاهين، فيستطيع الخادم دفع كل صفقة لحظة حدوثها. وتعرض البورصات والوسطاء بثّها الحي هكذا: تتصل وترسل رسالة subscribe تسمّي ما تريد وتتلقى سيلًا من رسائل JSON صغيرة.

# 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

قراءة الرسائل بأمان

كل ما يمر على الخط نص، وهذا النص ليس تحت سيطرتك. فكّ ترميزه بـ json.loads لا بـ eval التي تنفذ ما شاء المرسل كتابته. وتوقّع المفاجآت: كثيرًا ما تصل الأسعار نصوصًا لتجنب تقريب الفاصلة العائمة في الخادم، وليست كل رسالة صفقة. ترسل المصادر نبضات (heartbeats) كل بضع ثوانٍ لتميّز سوقًا هادئًا من اتصال ميت؛ تعرّف عليها وتجاوزها، واعتبر الصمت الطويل علامة على وجوب إعادة الاتصال.

# 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

الرسائل المفقودة وإعادة الاتصال

تنقطع الاتصالات، ولذلك يرقّم المصدر رسائله. إذا استلمت 7 ثم 11 فالرسائل 8 و9 و10 مفقودة وفي بياناتك ثغرة: اكتشفها ثم رمّمها مثلًا بطلب لقطة. وبعد الانقطاع تعيد الاتصال لكن لا فورًا ولا في اللحظة نفسها مع كل العملاء: انتظر base ثم ضعفها ثم ضعفها ثانية حتى حدٍّ أقصى. ويمنع هذا التراجع الأسي إغراق خادم متعثر. وفي الشيفرة الحقيقية تكون حلقة الاستقبال async for message in connection: داخل 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
ما لا يحتاجه المختبرليس في مختبر المتصفح شبكة، فتتدرّب على المنطق المحيط بالمصدر وهنا تسكن الأخطاء. وللانتقال إلى البث الحي ثبّت حزمة websockets ونفّذ await websockets.connect(url) وأرسل رسالة subscribe وشغّل حلقة async for. ولا تضع مفاتيح API في شيفرة تشاركها، وتوقّع الحاجة إلى المنطق نفسه لكل منصة بصيغة رسائلها الخاصة.
  • الـ websocket اتصال يبقى مفتوحًا في الاتجاهين: تشترك مرة واحدة ثم تستقبل سيلًا من الرسائل.
  • فكّ الترميز بـ json.loads لا eval؛ وتوقّع أسعارًا نصية وتجاوز النبضات.
  • أرقام التسلسل تكشف الرسائل المفقودة؛ وبعد الانقطاع أعد الاتصال بانتظار يتضاعف حتى حدٍّ أقصى.

التالي: بروتوكول FIX، صيغة الرسائل التي يستخدمها الوسطاء والبورصات لتقديم الأوامر.

المختبر: ما الذي يُقيَّم

  • اطلب مصدر بيانات باستخدام subscribe_message25 نقطة
  • اقرأ رسالة باستخدام parse_tick25 نقطة
  • اكتشف الرسائل المفقودة باستخدام find_gaps25 نقطة
  • خطّط لإعادة الاتصال باستخدام backoff_delays25 نقطة
المستكشف · lab-115-1
▾ lab-115-1
…
bash · داخل المتصفح
learner@quantcraft:~/lab-115-1$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

هل تريد إعادة ضبط هذا الموضوع؟

سيعود كل ما في lab-115-1 إلى الملفات الابتدائية وستُمسح الطرفية. ستفقد حلّك الحالي ولا يمكن التراجع عن ذلك.

سيُعاد ضبطه
  • main.py ← الشيفرة الابتدائية
  • سجل الطرفية ومخرجات الاختبارات
  • التغييرات غير المُرسلة
سيُحتفظ به
  • أفضل درجة في المختبر
  • نقاط XP والتتابع
  • تقدّم المحاضرة والملاحظات

سجّل الدخول لتشغيل الكود الخاص بك.

قراءة كل المحاضرات مجانية. أنشئ حسابًا أو سجّل الدخول لتشغيل الكود وتنفيذ الاختبارات وتسليم هذا المختبر.

  • بيئة تطوير داخل المتصفح مع طرفية bash — لا حاجة لتثبيت أي شيء
  • تصحيح فوري ونقاط خبرة ومكانك في المسار
إنشاء حسابتسجيل الدخول
أو
المتابعة عبر GitHub