← 前回:② 速度編 CPU vs WebGPU 並列リダクション
01
概要
シリーズ第3回(応用編)です。基本編・速度編で扱ったヨーロピアン・オプションは
満期の株価だけで価格が決まり、Black-Scholes解析解という"答え"がありました。
今回は、株価が満期までにどう動いたか(経路)に依存するエキゾチック・オプションを扱います。
💡 この記事で学べること
金融:アジアン・バリアオプション、なぜ解析解が無いか(経路依存性)
WebGPU:満期までの全ステップをGPU上でシミュレーション、経路の保持、条件分岐の並列処理
🎯 この記事の核心
エキゾチック・オプションの多くには閉じた解析解が存在しません。
つまりモンテカルロ法が"唯一の現実的手段"になります。
「解析解で答え合わせできない問題を、どう信頼するか」——シリーズの集大成です。
02
動作環境・注意事項
| Chrome 113+ | ✔ 推奨 |
| Edge 113+ | ✔ 動作します |
| Firefox | ✘ WebGPU 未対応(CPUフォールバック) |
| Safari 18+ | △ 実験的対応 |
| 計算量 | △ マルチステップは基本編より重い(パス数×ステップ数) |
03
金融の背景:エキゾチック・オプション
エキゾチック・オプションとは、標準的なヨーロピアン/アメリカンとは違う、
特殊な条件を持つオプションの総称です。本記事では代表的な2種を扱います。
ヨーロピアン(比較用)
payoff = max(ST − K, 0)
満期の株価 ST のみで決まる。経路は無関係。
✓ Black-Scholes 解析解あり
アジアン・オプション
payoff = max(avg(S) − K, 0)
満期株価ではなく、期間中の平均株価で決済。価格操作に強く、実務でよく使われる。
✗ 一般に解析解なし
バリア・オプション
バリア接触で発生/消滅
株価が一定水準(バリア)に触れたかでペイオフが変わる。ノックイン/ノックアウト。
✗ 経路依存で複雑
📊 なぜ解析解が無いのか
ヨーロピアンは「満期の株価分布」さえ分かれば積分で解けます。
しかしアジアンは「平均」、バリアは「途中で触れたか」という経路全体の情報が必要です。
経路の取りうるパターンは無限にあり、閉じた数式で表せません。
だから実際に経路をたくさん生成して平均するモンテカルロ法が必要になります。
05
動作原理:マルチステップ・シミュレーション
基本編では満期株価を1ステップで一気に計算しました。
しかし経路依存オプションでは、満期までの株価の動きを細かいステップに分けて追う必要があります。
STEP 01
1スレッド = 1経路
各スレッドが1本の株価経路を最初から最後までシミュレーションします。基本編と同じ「1パス1スレッド」ですが、中身がループになります。
STEP 02
ステップごとに株価更新
dt = T/steps ごとに、各ステップで新しい乱数を引いて株価を1歩進めます。これを steps 回繰り返して経路を生成します。
STEP 03
経路情報を集計
アジアンなら各ステップの株価を累積して平均を計算。バリアなら「バリアに触れたか」のフラグを更新し続けます。
STEP 04
ペイオフ決定
経路の最後に、集計した情報(平均株価・接触フラグ)からペイオフを計算します。あとは基本編同様に平均して割引。
⚙️ メモリの工夫:経路を保存しない
全パスの全ステップを保存すると、200万パス × 252ステップ = 5億個の値になりメモリが足りません。
そこで経路は保存せず、必要な集計値だけをスレッド内で更新します(オンザフライ集計)。
平均なら「累積和」、バリアなら「接触フラグ」だけを保持すれば十分です。
06
コードのポイント解説
マルチステップの経路生成ループ
// 各スレッドが1本の経路を最初から最後までシミュレーション
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3u) {
let pathId = gid.x;
if (pathId >= p.numPaths) { return; }
let dt = p.t / f32(p.steps);
let drift = (p.r - 0.5 * p.sigma * p.sigma) * dt;
let volStep = p.sigma * sqrt(dt);
var s = p.s0; // 現在の株価
var sumS = 0.0; // アジアン用:株価の累積
var hitBarrier = false; // バリア用:接触フラグ
// ステップごとに株価を1歩ずつ進める
for (var step = 0u; step < p.steps; step = step + 1u) {
// ステップ固有のシードで乱数生成
let z = randNormal(pathId * p.steps + step + p.seed);
s = s * exp(drift + volStep * z); // 幾何ブラウン運動
sumS = sumS + s;
if (s >= p.barrier) { hitBarrier = true; }
}
ハイライト部分が応用編の核心です。基本編の「1ステップで満期へジャンプ」と違い、
for ループで株価を1歩ずつ進めます。
ループ内で sumS(平均用の累積)と
hitBarrier(接触判定)を更新し続けるのがポイントです。
オプションタイプ別のペイオフ計算
// 経路の集計値からペイオフを決定
var payoff = 0.0;
if (p.optType == 0u) { // ヨーロピアン:満期株価
payoff = max(s - p.k, 0.0);
} else if (p.optType == 1u) { // アジアン:平均株価
let avgS = sumS / f32(p.steps);
payoff = max(avgS - p.k, 0.0);
} else { // バリア:接触で発生/消滅
let vanilla = max(s - p.k, 0.0);
if (p.isKnockOut > 0.5) {
// ノックアウト:触れたら無効
payoff = select(vanilla, 0.0, hitBarrier);
} else {
// ノックイン:触れたら有効
payoff = select(0.0, vanilla, hitBarrier);
}
}
payoffs[pathId] = payoff;
}
WGSLの select(a, b, cond) は三項演算子のような関数で、
cond が true なら b、false なら a を返します。
バリアの「接触したか」で支払いが変わる条件分岐を、分岐命令なしで書けます。
GPUでは分岐のダイバージェンス(スレッド間で分岐が割れること)を避けると効率的です。
ステップ間で乱数を変える(重要)
// 各ステップで独立した乱数が必要
// シードに「ステップ番号」を混ぜることで実現
let z = randNormal(pathId * p.steps + step + p.seed);
// ↑ パスごと ↑ ステップごと
// もし step を混ぜ忘れると…
// 全ステップで同じ乱数 → 株価が一直線に動く(経路にならない)
経路生成では各ステップで異なる乱数が必須です。
シードに pathId * steps + step を使うことで、
全パス・全ステップでユニークな乱数列を保証します。
step を混ぜ忘れると全ステップで同じ乱数になり、経路がギザギザにならず一直線になってしまいます。
07
ハマりどころまとめ
PITFALL 01
ステップ間の乱数
各ステップで乱数を変えないと経路が一直線に。シードにステップ番号を混ぜます。
PITFALL 02
離散モニタリング誤差
バリア接触をステップごとにしか見ないため、ステップ間で一瞬触れた場合を見逃します。ステップ数を増やすと精度が上がります。
PITFALL 03
計算量の爆発
計算量はパス数×ステップ数。252ステップ×200万パス=5億回。基本編より格段に重く、パス数を控えめにする配慮が必要です。
PITFALL 04
経路を保存しない設計
全経路保存はメモリ不足になります。集計値(累積和・フラグ)だけをスレッド内で更新するオンザフライ方式が基本です。
PITFALL 05
分岐ダイバージェンス
スレッド間でif分岐が割れるとGPU効率が落ちます。select() などで分岐を避けられる場合は避けます。
PITFALL 06
描画用パスの別計算
株価パスの軌跡描画は、価格評価とは別に少数(100本)の全ステップを保存して行います。価格評価本体とは分離します。
⚠️ 免責事項
本デモは WebGPU とモンテカルロ法の学習・技術実験を目的としたものです。
価格モデルは一定ボラティリティ・離散モニタリングなどの簡略化された仮定に基づきます。
実際の投資判断・オプション取引の根拠として使用しないでください。
オプション取引、特にエキゾチック・オプションは高いリスクを伴います。
FINAL — 実用ツール版
iseeit.jp:統合オプション価格評価ツール(グリークス + 実データ連携)
→