平均価格で支払う
通常のコールは、最終日の1日の価格だけで決まります。アジアンオプションは複数の日付にわたる平均価格で決まり、アジアン・コールは max(平均 - K, 0) を支払います。毎月の原油購入のように継続的に発生するコストのヘッジに企業が使い、満期時の価格操作もしにくい特徴があります。*経路依存*なので算術平均には単純なブラック=ショールズの公式がなく、通常の道具はモンテカルロ・シミュレーションです。
import numpy as np
# Two simulated paths: the starting price, then monthly prices; the last column is expiry
paths = np.array([[100.0, 90.0, 110.0, 130.0],
[100.0, 70.0, 80.0, 90.0]])
average = paths[:, 1:].mean(axis=1) # the monitoring dates only, not the starting price
print(average) # [110. 80.]
print(np.maximum(average - 100.0, 0.0)) # Asian call payoffs: [10. 0.]
print(np.maximum(paths[:, -1] - 100.0, 0.0)) # European call payoffs on the same paths: [30. 0.]経路をシミュレートする
経路は幾何ブラウン運動から作り、ドリフトはリスク中立のものを使います。mu の代わりに金利 r です。経路ごとに1行、観測日ごとに1列の標準正規乱数の行列を引き、対数価格の変化に変換して、各行に沿って累積和を取ります。これで1つの配列にすべての経路が入り、以降の処理(平均、ペイオフ)は行方向のベクトル化された操作1つで、経路に対するループは不要です。
import numpy as np
def simulate_paths(s0, rate, sigma, t, steps, n_paths, rng):
dt = t / steps
z = rng.standard_normal((n_paths, steps))
log_moves = (rate - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
return s0 * np.exp(np.hstack([np.zeros((n_paths, 1)), np.cumsum(log_moves, axis=1)]))
rng = np.random.default_rng(1)
paths = simulate_paths(100.0, 0.05, 0.2, 1.0, 12, 50000, rng) # monthly monitoring for a year
print(paths.shape) # (50000, 13)
print(round(float(paths[:, -1].mean()), 2), round(100 * np.exp(0.05), 2)) # the risk-neutral mean of the final price価格とその誤差
オプションの価格は割引した平均ペイオフ exp(-r * T) * mean(payoffs) です。モンテカルロの価格は推定値なので、必ず標準誤差 std(payoffs) / sqrt(経路数)(割引後)を併記します。数字が真値からどれだけ離れうるかを示し、経路数を4倍にすると半分になります。同じ株のアジアン・コールはヨーロピアン・コールより安く、平均が価格を滑らかにして実効ボラティリティを下げるためです。
import numpy as np
def simulate_paths(s0, rate, sigma, t, steps, n_paths, rng):
dt = t / steps
z = rng.standard_normal((n_paths, steps))
log_moves = (rate - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
return s0 * np.exp(np.hstack([np.zeros((n_paths, 1)), np.cumsum(log_moves, axis=1)]))
def asian_price(paths, strike, rate, t):
payoff = np.maximum(paths[:, 1:].mean(axis=1) - strike, 0.0)
discount = np.exp(-rate * t)
return discount * payoff.mean(), discount * payoff.std(ddof=1) / np.sqrt(len(payoff))
rng = np.random.default_rng(1)
paths = simulate_paths(100.0, 0.05, 0.2, 1.0, 12, 100000, rng)
price, error = asian_price(paths, 100.0, 0.05, 1.0)
european = np.exp(-0.05) * np.maximum(paths[:, -1] - 100.0, 0.0).mean()
print(round(float(price), 3), "+/-", round(float(error), 3))
print(round(float(european), 3), "European call on the same paths")# Geometric and arithmetic averages of the same monitoring prices
import numpy as np
prices = np.array([90.0, 110.0, 130.0])
arithmetic = prices.mean()
geometric = np.exp(np.log(prices).mean())
print(round(float(arithmetic), 3), round(float(geometric), 3)) # 110.0 108.774: the geometric average is never larger幾何平均には公式がある対数正規分布の積は対数正規なので、幾何平均に対するアジアン・オプションはブラック=ショールズ型の公式で値付けできます。そのため検算に便利で、さらに算術平均版のコントロール変量として価値があります。2つのペイオフは強く相関するので、幾何版の既知の厳密価格がモンテカルロのノイズの多くを打ち消せます。後のトピック(コントロール変量)がまさにこの考え方を使います。
- アジアンオプションは観測日の平均で支払う:
max(平均 - K, 0)。経路依存で単純な公式はなく、モンテカルロが道具になる - 全経路を1つの配列(行=経路、列=日付)にリスク中立ドリフトで作る。平均とペイオフは行方向でベクトル化できる
- 価格=
exp(-r * T) * mean(payoffs)、標準誤差はstd / sqrt(paths)。平均化でヨーロピアン・コールより安くなり、幾何版はコントロール変量になる
次はバリアオプションです。価格がある水準に触れると発生または消滅します。
ラボ:採点される内容
average_priceで各経路の平均を取る25点geometric_averageで幾何平均を試す25点asian_call_payoffで平均価格に基づく支払を求める25点asian_priceで誤差付きで値付けする25点