今回から、金融工学におけるシミュレーションについて学んでいく。テキストとして以下を使う。今回はP.59-P.67まで。
5. 分散減少法
法は
- 問題に沿った(同時)分布に従う(多変量)乱数列の生成
- その乱数列を使った計算
の2つの部分に分けて考えられる。乱数列の生成は更に
- 一様分布に従う乱数列
の生成
- それを元にした必要な同時分布に従う乱数列
の生成
に分けられる。
法は高次元問題を取り扱うことはできるものの、誤差を減らすには大きな時間が掛かり時間効率が低い。そこで本章では時間効率を上げるための手段の1つとして分散減少法を説明する。
法における解の統計的誤差は点列数
に対して
に出来る。分散減少法はこの
を減らす試みである。
法による関数
の積分値を
と書くことにすると、法のアイディアは以下の2つに分けることが出来ると言える:
- 関数
を同じ積分値を取る別の関数に置換え
- 期待値
の取り方を変更
関数 |
期待値 |
(1) 負の相関法 | (1) 条件付き |
(2) 制御変量法 | (2) 層別化法 |
(3) 回帰分析法 | (2-1) ラテン・ハイパーキューブ法 |
(4) マルチンゲール分散法 | (2-2) |
(3) 加重サンプリング法 | |
(4) 測度変換法 |
5.2 負の相関法
負の相関法による積分
の解をとする。負の相関法では関数
を関数
で置き換える。このとき、
に注意すれば
が成立する。したがって
で評価できる。
負の相関法では、の評価回数
倍になるので、同じ数の乱数を用いた場合、誤差評価が単純な
法に対する比べて半分未満になれば効率的である。負の相関法における解の誤差分散は
であるから、もし
ならば、負の相関法の単純な法に対する効果は
となり、分散減少法が機能する。
5.3 例①:定積分
定積分
に対して負の相関法を適用する。ただしは区間
を定義域とする任意の密度関数とし、これに対応する分布関数を
とし、その逆関数を
は所与、乱数
とする。
変数変換により区間
の定積分
と書き直す。これに負の相関法を適用して、
として得られる。ここでである。
すなわち
(1) | |
(2) | 乱数 |
(3) | |
(4) | |
(5) | |
(6) | |
(7) |
5.4 例②:ヨーロピアン・コール・オプションのMonte Carlo法による評価
ヨーロピアン・コール・オプション価格を単純な法および負の相関法で計算し、負の相関法の効果を評価する。なお行使価格は
、満期を
とする。
原資産価格の満たす確率微分方程式を
とする。このとき満期では
が成り立つ。したがってとして
により満期株価を生成できる。
これを基にペイオフ関数を
をもつヨーロピアン・コール・オプション価格
を推計する。
5.5 ヨーロピアン・コール・オプションのシミュレーション結果
実際にシミュレーションして負の相関法の精度を評価する。
通常の法で評価したヨーロピアン・コール・オプションの価格と負の相関法で評価したヨーロピアン・コール・オプションの価格を
-
方程式による解析解と比較した相対誤差を比較評価することにしたい。シミュレーション回数
は
と
ずつ増やしていった。なお入力パラメータは以下の通りとする:
初期時点における原資産価格 |
|
行使価格 |
|
無リスク金利 |
|
ボラティリティ |
|
満期までの期間 |
5.5.1 実装
using System; using System.Collections.Generic; using System.Linq; using System.IO; using System.Text; using System.Threading.Tasks; using MathNet.Numerics; namespace MonteCarlo { /// <summary> /// Monte Carlo Simulation /// </summary> public class MonteCarloSimulation { /// <summary> /// 2つの実数のうち大きい方を返す /// </summary> /// <param name="x">実数</param> /// <param name="y">実数</param> /// <returns>x>=yならばxを、そうでなければyを返す。</returns> private static double Max(double x, double y) { double ret = x + y; ret += Math.Abs(x - y); ret *= 0.5; return ret; } public static double OptionPricing(double S, double K, double r, double sigma, double T, int CallPutFlg) { if (CallPutFlg == 0 | CallPutFlg == 1) { double flg = -2.0 * CallPutFlg + 1.0; double d = Math.Log(S / K) + (r + 0.5 * Math.Pow(sigma, 2.0)) * T; d /= sigma * Math.Sqrt(T); double Price = S * MathNet.Numerics.Distributions.Normal.CDF(0.0, 1.0, flg * d); Price = flg * Price - flg * K * Math.Exp(-r * T) * MathNet.Numerics.Distributions.Normal.CDF(0.0, 1.0, flg * (d - sigma * Math.Sqrt(T))); return Price; } else { Console.WriteLine("CallPutFlg must be either 0 or 1."); return -99999; } } static void Main(string[] args) { // 出力先パス string OutPath = @"...適当な出力先..."; // オプションの設定 double S0 = 100; double r = 0.02; //年次 double std = 0.05; //年次 double K = 80; int T = 50; //日数 // 日次に直す r /= 250; std /= Math.Sqrt(250); // モンテカルロ法の試行回数は変更する var TestNum = new List<double>(); for (int i = 1; i <= 10000; i++) { TestNum.Add(i * 10); } var obj = new MathNet.Numerics.Random.MersenneTwister(); // テスト結果 List<double> EachResult1 = new List<double>(); List<double> EachResult2 = new List<double>(); List<double> EachEffect = new List<double>(); double Effect1 = 0.0; //効果の分母 double Effect2 = 0.0; //効果の分子 // 個々の結果 double sum1 = 0.0; double sum2 = 0.0; Console.WriteLine("Start Simulation Section ..."); // Test実施 for (int i = 0; i < TestNum.Count(); i++) { Console.WriteLine(" Start " + (i+1) +"/"+TestNum.Count()+"-th test..."); for (int j = 0; j < TestNum[i]; j++) { //満期原資産価格 double RN1 = obj.NextDouble(); RN1 = MathNet.Numerics.Distributions.Normal.InvCDF(0.0, 1.0, RN1); double ST1 = S0 * Math.Exp((r - 0.5 * Math.Pow(std, 2.0)) * T + std * Math.Sqrt(T) * RN1); double Payoff1 = Math.Exp(-r * T) * Max(ST1 - K, 0); sum1 += Payoff1; double ST2 = S0 * Math.Exp((r - 0.5 * Math.Pow(std, 2.0)) * T - std * Math.Sqrt(T) * RN1); double Payoff2 = Math.Exp(-r * T) * Max(ST2 - K, 0); sum2 += 0.5 * (Payoff1 + Payoff2); Effect1 += Math.Pow(Payoff1, 2.0); Effect2 += Math.Pow(0.5 * (Payoff1 + Payoff2), 2.0); } // Monte Carlo法による価格 sum1 /= TestNum[i]; sum2 /= TestNum[i]; // 格納 EachResult1.Add(sum1); EachResult2.Add(sum2); // 効果 Effect1 -= TestNum[i] * Math.Pow(sum1, 2.0); Effect2 -= TestNum[i] * Math.Pow(sum2, 2.0); EachEffect.Add(Effect2 / Effect1); // 初期化 sum1 = 0.0; sum2 = 0.0; Effect1 = 0.0; Effect2 = 0.0; } double BSCall = OptionPricing(S0, K, r, std, T, 0); // 出力 Console.WriteLine("Start Output Section..."); string Path = OutPath + "01_MonteCarloEvaluationByEuropeanCallOptionPricing.csv"; try { StreamWriter Stream = new StreamWriter(Path, false, Encoding.UTF8); Stream.WriteLine(string.Format("No,TestNum, BS, C1, C2, Abs(C1), Abs(C2), Rel(C1),Rel(C2), Effect")); for (int i = 0; i < EachResult1.Count(); i++) { Stream.WriteLine(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}", i + 1, TestNum[i], BSCall, EachResult1[i], EachResult2[i], Math.Abs(EachResult1[i] - BSCall), Math.Abs(EachResult2[i] - BSCall), Math.Abs(EachResult1[i] - BSCall) / BSCall, Math.Abs(EachResult2[i] - BSCall) / BSCall, EachEffect[i])); } Stream.Close(); } catch (Exception e) { Console.WriteLine(e.Message); } } } }
5.5.2 ヨーロピアン・コール・オプションのシミュレーション結果
相対誤差は以下のグラフの通りとなった。相対誤差のオーダーがという形式であるから、常用対数を取った
を
軸にとって表示している。
図1 シミュレーション回数を変えたときのBlack-Scholes方程式の解との相対誤差の推移

ここから明らかなように、負の相関法は倍精度が良い。