σμ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›الوحدة 4 · تسعير الدخل الثابت والخيارات›الموضوع 4.13·~50 دمتوسط

التقلب الضمني

الموضع في المسارالوحدة 4 من 11 · الموضوع 13 من 16
4.13 التقلب الضمني
—درجة المختبر— / 100إجمالي XP—التتابع—

تشغيل الصيغة بالعكس

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

import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

market = 8.0                                   # the quoted price of the 100-strike, 6-month call on a stock at 100
for sigma in (0.10, 0.15, 0.20, 0.25, 0.30):
    model = bs_call(100, 100, 0.05, sigma, 0.5)
    print(sigma, round(model, 4), "error", round(model - market, 4))

لماذا توجد إجابة واحدة فقط

يرتفع سعر الكول باطراد مع sigma: فيغا موجبة، فيقطع الخطأ الصفر مرة واحدة فقط. وهذا يجعل الجواب وحيدًا ويدل على اتجاه الحركة: إن كان النموذج غاليًا خفّض sigma. ولا يوجد حل إلا إذا وقع سعر السوق بين *الحد الأدنى الجوهري* max(S - K * exp(-rT), 0) (تقلب صفر) والسعر الفوري S (تقلب لانهائي) بدقة. وخارج هذين الحدين يناقض السعر انعدام المراجحة، فالإجابة الصحيحة خطأ لا رقم.

# The bounds: no volatility can explain a price outside them
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

spot, strike, rate, t = 100, 90, 0.05, 1
floor = max(spot - strike * math.exp(-rate * t), 0)
print(round(floor, 4), spot)                                 # 14.3894 100
print(round(bs_call(spot, strike, rate, 0.0001, t), 4))      # 14.3894   tiny volatility: the floor
print(round(bs_call(spot, strike, rate, 5.0, t), 4))         # 98.8513   huge volatility: close to the spot

تنصيف المجال

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

# Implied volatility by halving the interval, then reprice to check it
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def implied_vol(price, spot, strike, rate, t, tol=1e-8):
    low, high = 0.0001, 5.0
    while high - low > tol:
        mid = (low + high) / 2
        if bs_call(spot, strike, rate, mid, t) > price:
            high = mid
        else:
            low = mid
    return (low + high) / 2

iv = implied_vol(8.0, 100, 100, 0.05, 0.5)
print(round(iv, 6))                                        # 0.240534
print(round(bs_call(100, 100, 0.05, iv, 0.5), 6))          # 8.0

الابتسامة

لو كانت بلاك-شولز صحيحة تمامًا لأوحت كل الخيارات على السهم نفسه والتاريخ نفسه بالتقلب نفسه. لكن الواقع غير ذلك: فأسعار التنفيذ المنخفضة توحي عادة بتقلب أعلى من المرتفعة (*الانحراف*)، أو يكون الجناحان أعلى من الوسط (*الابتسامة*). ويُظهر النمط ما يفوت النموذج (القفزات والذيول السميكة والطلب على الحماية)، ولهذا تُعلن الخيارات بالتقلب الضمني: رقم واحد قابل للمقارنة عبر أسعار التنفيذ والآجال، ومنه يُشتق السعر.

# A smile: market prices at five strikes, converted to implied volatilities
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def implied_vol(price, spot, strike, rate, t, tol=1e-8):
    low, high = 0.0001, 5.0
    while high - low > tol:
        mid = (low + high) / 2
        low, high = (low, mid) if bs_call(spot, strike, rate, mid, t) > price else (mid, high)
    return (low + high) / 2

strikes = [90, 95, 100, 105, 110]
quotes = [bs_call(100, k, 0.05, v, 0.5) for k, v in zip(strikes, [0.28, 0.24, 0.21, 0.22, 0.25])]   # prices from a made-up smile
for strike, quote in zip(strikes, quotes):
    print(strike, round(quote, 4), round(implied_vol(quote, 100, strike, 0.05, 0.5), 4))
السعر والتقلب هما العرض نفسهيعلن صنّاع السوق تقلبًا ويحوله المشتري إلى سعر بالصيغة المتفق عليها؛ والتحويل اصطلاح لا إيمان بالنموذج. ولأن التناظر واحد لواحد لسعر تنفيذ وتاريخ ومعدل معينة، فلا يضيع شيء في أي اتجاه. المهم هو المُدخل: فالتقلب الضمني لا تزيد جودته عن جودة المعدل والعائد الموزع والزمن الذي تعطيه له.
  • التقلب الضمني هو sigma التي تجعل bs_call تساوي سعر السوق: يُوجد بالبحث لعدم وجود صيغة معكوسة.
  • يرتفع السعر مع sigma: فهناك جواب واحد وإشارة الخطأ تدل على الاتجاه؛ وخارج الحدين لا جواب.
  • تنصيف المجال يتقارب بسرعة (نحو 30 خطوة لثمانية أرقام)؛ والابتسامة هي التقلب الضمني مقابل سعر التنفيذ.

التالي: إيجاد الجذور بالتنصيف ونيوتن، الطريقة العامة وراء هذا البحث وطريقة أسرع بكثير.

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

  • قارن النموذج بالسوق باستخدام call_price_error25 نقطة
  • ابحث في شبكة من التقلبات باستخدام implied_vol_scan25 نقطة
  • اعكس الصيغة باستخدام implied_vol25 نقطة
  • اقرأ ابتسامة كاملة باستخدام implied_vol_smile25 نقطة
المستكشف · lab-78-1
▾ lab-78-1
…
bash · داخل المتصفح
learner@quantcraft:~/lab-78-1$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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

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

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

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