σμ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.15·~55 分中級

二項ツリー

ロードマップ上の位置モジュール 4/11 · トピック 15/16
4.15 二項ツリー
—ラボスコア— / 100合計XP—連続日数—

上がるか下がるかしかない株

ブラック=ショールズは公式ですが、二項ツリーは手順であり、公式では扱えないものも値付けできます。満期までの時間を小さなステップに分け、各ステップで株価は u 倍に上がるか d 倍に下がるかのどちらかだけとします。n ステップ後には n + 1 通りの株価があります。満期のペイオフは分かっており、それより前の各ノードは、そこから続く2つのノードの割引平均の価値なので、ツリーを現在まで後ろ向きに埋められます。

# One step: a stock at 100 goes to 110 or 90. What is a call with strike 100 worth?
import math

spot, strike, rate, dt = 100, 100, 0.05, 1.0
u, d = 1.10, 0.90
p = (math.exp(rate * dt) - d) / (u - d)             # the probability that makes the stock grow at the rate
up_value, down_value = max(spot * u - strike, 0), max(spot * d - strike, 0)
price = math.exp(-rate * dt) * (p * up_value + (1 - p) * down_value)
print(round(p, 4), round(up_value, 4), down_value)   # 0.7564 10.0 0
print(round(price, 4))                               # 7.1947

この確率は予測ではない

数 p は、株価が平均して無リスク金利で成長するように選びます:p * u + (1 - p) * d = exp(rate * dt)、したがって p = (exp(rate * dt) - d) / (u - d) です。これはリスク中立確率で、複製ポートフォリオを割引期待値に等しくするための道具であり、株価がどこへ向かうかについての見方ではありません。観測されたボラティリティに合わせるため、コックス=ロス=ルービンシュタイン(CRR)のツリーは u = exp(sigma * sqrt(dt))、d = 1 / u を使います。上昇のあとの下落は元の価格に戻るのでツリーは再結合し、n ステップでも終端ノードは n + 1 個だけです。

# The CRR parameters and the prices at expiry of a 3-step tree
import math

def crr_parameters(rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    return u, d, p

def terminal_prices(spot, u, d, steps):
    return [spot * u ** j * d ** (steps - j) for j in range(steps + 1)]

u, d, p = crr_parameters(0.05, 0.2, 1, 3)
print(round(u, 4), round(d, 4), round(p, 4))                     # 1.1224 0.8909 0.5438
print([round(price, 2) for price in terminal_prices(100, u, d, 3)])

後ろ向きに計算する

満期では各ノードはペイオフそのもので、コールなら max(S - K, 0) です。1列前では各値が exp(-rate * dt) * (p * value_up + (1 - p) * value_down) となり、列のノードは1つ減ります。これを n 回繰り返すと値が1つ残り、それが今日の価格です。Python では1列がリストで、1回の掃引は隣り合うペアに対するリスト内包表記です。ステップ数を増やすと価格はブラック=ショールズの価格に収束し、ツリーは公式の結果を再現します。

# The tree price of a European call approaches the Black-Scholes price as steps grow
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 european_call_tree(spot, strike, rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(spot * u ** j * d ** (steps - j) - strike, 0) for j in range(steps + 1)]
    for _ in range(steps):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(len(values) - 1)]
    return values[0]

print("Black-Scholes", round(bs_call(100, 100, 0.05, 0.2, 1), 4))
for steps in (1, 5, 25, 100, 400):
    print(steps, round(european_call_tree(100, 100, 0.05, 0.2, 1, steps), 4))

早期行使

ツリーの本当の強みは、公式では値付けできないオプションです。アメリカンオプションはいつでも行使できます。各ノードで保有者は2つの値、保有を続けたときの割引期待値と今すぐ行使したときのペイオフを比べ、大きいほうを取ります。アルゴリズムの変更点は、各ノードでの max がひとつ増えるだけです。配当のない株のコールでは早期行使は得になりませんが、プットでは得になりえます。深くイン・ザ・マネーのプットは今日の現金のほうが価値が高いため、アメリカン・プットはヨーロピアンより高くなります。

# An American put against a European put: the early-exercise premium
import math

def tree(spot, strike, rate, sigma, t, steps, american):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(strike - spot * u ** j * d ** (steps - j), 0) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(i + 1)]
        if american:
            values = [max(values[j], strike - spot * u ** j * d ** (i - j)) for j in range(i + 1)]
    return values[0]

for spot in (80, 100, 120):
    european = tree(spot, 100, 0.05, 0.2, 1, 200, False)
    american = tree(spot, 100, 0.05, 0.2, 1, 200, True)
    print(spot, round(european, 4), round(american, 4), "premium", round(american - european, 4))
遅くても汎用的な方が、速くて特殊なものに勝る単純なヨーロピアン・オプションなら閉じた公式が一瞬で正確なので、ツリーは使いません。ツリーが役立つのは、ペイオフに判断(早期行使)、バリア、離散配当などが含まれるときで、各ノードにその規則を加えます。代償は時間で、n ステップはおよそ n**2 / 2 回のノード更新になり、収束も滑らかではありません(行使価格がノードの間のどこに入るかで誤差が揺れる)。そのためツリーは複数のステップ数で比べて検証します。
  • 各ステップで株価は u 倍か d 倍になる。CRR の選び方 u = exp(sigma * sqrt(dt))、d = 1 / u でツリーは再結合し、n ステップの終端ノードは n + 1 個
  • 後ろ向きに値付けする。満期のペイオフから始め、各ノードで exp(-rate * dt) * (p * up + (1 - p) * down)。p はリスク中立確率で予測ではない
  • ステップを増やすとブラック=ショールズに収束する。アメリカン・オプションは各ノードに max(継続, 行使) を1つ加えるだけで、これがツリーの存在理由

次は有限差分法です。オプションの偏微分方程式を格子上で解いて値付けします。

ラボ:採点される内容

  • crr_parameters で上昇と下落の幅を決める25点
  • terminal_prices で満期時の株価を並べる25点
  • european_call_tree でツリーを後ろ向きにたどる25点
  • american_put_tree で早期行使を認める25点
エクスプローラー · lab-80-1
▾ lab-80-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-80-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-80-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける