σμ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›الوحدة 10 · هندسة Python عالية الأداء›الموضوع 10.6·~1 سمتقدم

Numba

الموضع في المسارالوحدة 10 من 11 · الموضوع 6 من 13
10.6 Numba
—درجة المختبر— / 100إجمالي XP—التتابع—

ترجمة الحلقات التي كتبتها

بعض الحسابات حلقة بطبيعتها: كل خطوة تعتمد على السابقة كما في المتوسط الموزون أسّيًا أو السحب أو عائد خيار يعتمد على المسار. ولا يستطيع NumPy توجيهها، وحلقة بايثون على مليون عنصر بطيئة لأن كل خطوة تمر بالمفسّر. وNumba مترجم في وقت التشغيل: تضع @njit فوق دالة وعند أول نداء يترجم Numba حلقة بايثون إلى شيفرة آلة للأنواع التي أُعطيت بالضبط. وتجري النداءات اللاحقة بسرعة قريبة من C، غالبًا أسرع بخمسين إلى مئة مرة من الحلقة نفسها في بايثون الصرفة، دون تغيير الشيفرة.

# An exponentially weighted average is a loop: each value depends on the one before
import numpy as np

try:
    from numba import njit                           # one decorator compiles the function when it is installed
except ImportError:
    def njit(func):                                  # without Numba the same code simply runs as Python
        return func

@njit
def ewma(values, alpha):
    out = np.empty(len(values))
    out[0] = values[0]
    for i in range(1, len(values)):
        out[i] = alpha * values[i] + (1.0 - alpha) * out[i - 1]
    return out

print(ewma(np.array([1.0, 2.0, 3.0]), 0.5))          # [1.   1.5  2.25]

الأسلوب الذي يحبه Numba

في وضع nopython الذي يختاره @njit يترجم Numba مجموعة جزئية من بايثون فقط ويرفض الباقي بدل الرجوع بصمت إلى شيفرة بطيئة. وهو يفهم الأعداد ومصفوفات NumPy والـ tuples والحلقات البسيطة وif وجزءًا كبيرًا من دوال NumPy. ولا يفهم pandas ولا كائنات بايثون العشوائية ولا القواميس بأنواع مختلطة. فاكتب الدالة الساخنة بالأسلوب البسيط: تأخذ مصفوفات وأعدادًا مفردة، وتحجز الناتج بـ np.empty أو np.zeros، وتدور بـ for i in range(n)، وتحتفظ بمراكم عددية (total, peak). وناديها بأنواع الوسائط نفسها كل مرة لأن تركيبة أنواع جديدة تطلق ترجمة جديدة.

# A drawdown is a running peak and a running worst case: two scalars and one loop
import numpy as np

try:
    from numba import njit
except ImportError:
    def njit(func):
        return func

@njit
def max_drawdown(equity):
    peak = 0.0
    worst = 0.0
    for i in range(len(equity)):
        value = equity[i]
        if value > peak:
            peak = value
        drawdown = (peak - value) / peak
        if drawdown > worst:
            worst = drawdown
    return worst

curve = np.array([100.0, 120.0, 90.0, 130.0, 117.0])
print(max_drawdown(curve))                           # 0.25   from 120 down to 90
print(max_drawdown(np.array([1.0, 2.0, 3.0])))       # 0.0    a curve that only rises

زمن الترجمة والتخزين المؤقت وأين لا يفيد Numba

يدفع النداء الأول ثمن الترجمة، غالبًا ثانية أو أكثر، فقِس زمن دالة Numba بعد استدعائها مرة على بيانات صغيرة، واستعمل @njit(cache=True) لحفظ الشيفرة المترجمة بين التشغيلات. ويحتاج التسريع عملًا كافيًا: دالة تُستدعى مليون مرة على ثلاثة أرقام تقضي وقتها في كلفة النداء لا في الحلقة. ويفيد Numba في الحلقات على المصفوفات؛ ولا يفعل شيئًا لشيفرة هي أصلًا نداء NumPy واحد ولا لانتظار قاعدة بيانات ولا لخوارزمية سيئة. ويمكن لـ @njit(parallel=True) مع prange توزيع تكرارات مستقلة على الأنوية، ويبادل fastmath=True شيئًا من صرامة الفاصلة العائمة بالسرعة، ولا تفعّله إلا بعد التأكد من بقاء النتائج ضمن التسامح.

# Counting crossings: Numba compiles any loop like this, so keep the logic in scalars
import numpy as np

try:
    from numba import njit
except ImportError:
    def njit(func):
        return func

@njit(cache=False)
def count_crossovers(fast, slow):
    count = 0
    previous = 0.0
    for i in range(len(fast)):
        gap = fast[i] - slow[i]
        if i > 0 and previous * gap < 0:
            count += 1
        previous = gap
    return count

fast = np.array([1.0, 3.0, 2.0, 1.0, 4.0])
slow = np.full(5, 2.0)
print(count_crossovers(fast, slow))                  # 2     below, above, touch, below, above: two strict sign changes
# Options change how a function is compiled; here they are ignored if Numba is not installed
import numpy as np

try:
    from numba import njit, prange
except ImportError:
    prange = range

    def njit(*args, **kwargs):
        if args and callable(args[0]):
            return args[0]
        return lambda func: func

@njit(cache=True)                                    # keep the compiled code on disk between runs
def total_notional(prices, quantities):
    total = 0.0
    for i in prange(len(prices)):                    # prange runs in parallel only with parallel=True
        total += prices[i] * quantities[i]
    return total

prices = np.array([184.25, 410.5, 99.0])
quantities = np.array([100.0, 50.0, 200.0])
print(total_notional(prices, quantities))            # 58750.0
print(total_notional(prices, quantities) == float(prices @ quantities))     # True  same answer as the vectorised dot product
تحقق من الإجابات لا من السرعة فقطقد تخطئ الحلقة المترجمة بطرق جديدة: فيض أعداد صحيحة بدل أعداد بايثون غير المحدودة، أو مصفوفة float32 مستعملة بصمت حيث توقعت float64، أو فهرس خارج الحدود يقرأ قمامة بدل رفع خطأ. قارن النتيجة المترجمة بنسخة بايثون العادية على مدخلات عشوائية (بـ fn.py_func الذي يحتفظ به Numba) كما تفعل اختبارات هذه المختبرات، وفعّل فحص الحدود أثناء التطوير.
  • @njit يترجم دالة بايثون إلى شيفرة آلة عند أول نداء؛ وتصير الحلقات المعتمدة على الخطوات السابقة سريعة دون توجيه.
  • اكتب لوضع nopython: مصفوفات وأعداد مفردة وناتج محجوز مسبقًا وfor i in range(n) ومراكم عددية؛ دون pandas أو حاويات بأنواع مختلطة.
  • قِس الزمن بعد نداء تسخين واستعمل cache=True ولا تتوقع مكسبًا في شيفرة هي أصلًا نداء NumPy واحد، وتحقق من النتائج بنسخة بايثون العادية.

التالي: Cython، طريقة أخرى لترجمة حلقات بايثون بإضافة الأنواع وبناء وحدة امتداد.

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

  • احتفظ بمجموع جارٍ في rolling_mean25 نقطة
  • تتبّع القمة في max_drawdown25 نقطة
  • عُدّ التقاطعات في count_crossovers25 نقطة
  • املأ مصفوفة ناتج في ewma25 نقطة
المستكشف · lab-122-1
▾ lab-122-1
…
bash · داخل المتصفح
learner@quantcraft:~/lab-122-1$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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

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

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

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