σμ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›الوحدة 6 · الاختبار الرجعي وتكاليف التداول›الموضوع 6.2·~1 س 15 دمتقدم

أداة اختبار رجعي قائمة على الأحداث

الموضع في المسارالوحدة 6 من 11 · الموضوع 2 من 6
6.2 أداة اختبار رجعي قائمة على الأحداث
—درجة المختبر— / 100إجمالي XP—التتابع—

شمعة واحدة في كل مرة

يتصرف الاختبار الرجعي المدفوع بالأحداث مثل نظام حي: يتقدم الزمن شمعة شمعة، وفي كل شمعة لا يرى البرنامج إلا الماضي، ويقرر ويرسل أمرًا ويحدّث حسابه. وهو أبطأ من الاختبار المتجه، لكنه يحمل حالة (نقد وأسهم) ويستطيع نمذجة الأوامر والتنفيذ، ويمكن لشيفرة الاستراتيجية نفسها أن تعمل لاحقًا على بث حي. والحلقة هي الجوهر: for على الشموع والحساب ينتقل من دورة إلى التالية.

def crossover_signal(history, fast, slow):
    if len(history) < slow:
        return 0                                           # not enough history yet
    fast_mean = sum(history[-fast:]) / fast
    slow_mean = sum(history[-slow:]) / slow
    return 1 if fast_mean > slow_mean else 0

prices = [100, 101, 103, 102, 105, 107, 104, 103, 99, 98]
print(crossover_signal(prices[:3], 2, 4))                  # 0: only 3 prices
print(crossover_signal(prices[:6], 2, 4))                  # 1: the 2-bar mean is above the 4-bar mean
print(crossover_signal(prices[:9], 2, 4))                  # 0: the trend has turned

الأوامر تغيّر الحساب

يحوّل الأمر النقد إلى أسهم أو العكس. ومع الأسهم الصحيحة يترك شراء int(cash // price) بعض النقد، وقيمة الحساب دائمًا cash + shares * price. وإبقاء منطق الأوامر في دالة صغيرة execute_order يقصّر الحلقة ويتيح إضافة الرسوم والانزلاق لاحقًا في موضع واحد. وبيع كل شيء هو ببساطة cash + shares * price وصفر أسهم.

def execute_order(cash, shares, price, target):
    if target == 1:
        bought = int(cash // price)                        # whole shares only
        return cash - bought * price, shares + bought
    return cash + shares * price, 0

cash, shares = execute_order(1000.0, 0, 103.0, 1)
print(cash, shares, cash + shares * 103.0)                 # 73.0 9 1000.0
cash, shares = execute_order(cash, shares, 99.0, 0)
print(cash, shares)                                        # 964.0 0

الحلقة وما يحق لها معرفته

في الشمعة t يجوز للاستراتيجية استخدام prices[:t] التي تنتهي عند الإغلاق السابق، وتتداول بسعر prices[t]. وقطع prices[:t] هو ما يُبقي المستقبل بعيدًا: فالسعر التالي ببساطة ليس في القائمة. ومن هنا اختبار جيد لمحرك الأحداث: تشغيله على أول k شمعة فقط يجب أن يعطي أول k نتيجة من التشغيل الكامل. وإن اختلفت فقد تلصصت الاستراتيجية.

def crossover_signal(history, fast, slow):
    if len(history) < slow:
        return 0
    return 1 if sum(history[-fast:]) / fast > sum(history[-slow:]) / slow else 0

def execute_order(cash, shares, price, target):
    if target == 1:
        bought = int(cash // price)
        return cash - bought * price, shares + bought
    return cash + shares * price, 0

def run_backtest(prices, fast, slow, cash):
    shares = 0
    values = []
    for t, price in enumerate(prices):
        if t > 0:
            target = crossover_signal(prices[:t], fast, slow)       # only what was known at the last close
            cash, shares = execute_order(cash, shares, price, target)
        values.append(cash + shares * price)
    return values

prices = [100, 101, 103, 102, 105, 107, 104, 103, 99, 98]
full = run_backtest(prices, 2, 4, 1000.0)
print([round(v, 2) for v in full])
print(run_backtest(prices[:6], 2, 4, 1000.0) == full[:6])             # True: the future never leaks
# The benchmark: buy once at the start and hold
import random

def buy_and_hold(prices, cash):
    shares = int(cash // prices[0])
    left = cash - shares * prices[0]
    return [left + shares * price for price in prices]

random.seed(3)
prices = [100.0]
for _ in range(99):
    prices.append(round(prices[-1] * (1 + random.gauss(0.0004, 0.012)), 2))
held = buy_and_hold(prices, 10000.0)
print(round(held[0], 2), round(held[-1], 2))
الشيفرة نفسها قد تعمل حيّةتنظر الحلقة إلى شمعة وتتصرف: وهذا هو شكل برنامج التداول الحي تمامًا، حيث تصل الشمعة التالية من بث بيانات لا من قائمة. وفصل منطق الإشارة والأمر والحساب في دوال مستقلة هو ما يتيح استبدال البث التاريخي ببث حي دون إعادة كتابة الاستراتيجية.
  • يمر الاختبار المدفوع بالأحداث على الشموع بحالة (نقد وأسهم)؛ وفي الشمعة t لا يستخدم إلا prices[:t].
  • تنقل الأوامر بين النقد والأسهم الصحيحة: قيمة الحساب cash + shares * price؛ أبقِ منطق الأوامر في دالة واحدة.
  • اختبر النظر إلى المستقبل: تشغيل أول k شمعة يجب أن يساوي أول k نتيجة من التشغيل الكامل؛ وقارن بالشراء والاحتفاظ.

التالي: انحياز النظر إلى المستقبل، الخطأ الصامت الذي يجعل الاختبار الرجعي أجمل من أن يكون حقيقيًا.

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

  • قارن متوسطين باستخدام crossover_signal25 نقطة
  • نفّذ الأمر باستخدام execute_order25 نقطة
  • اعبر الشموع شمعة شمعة باستخدام run_backtest25 نقطة
  • حدّد المقياس المرجعي باستخدام buy_and_hold25 نقطة
المستكشف · lab-88-1
▾ lab-88-1
…
bash · داخل المتصفح
learner@quantcraft:~/lab-88-1$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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

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

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

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