

色々あって積立投資シミュレーションが作りたくなったらしい。
とりあえず、ググってみると
が見つかった。 pythonか~。
ワイはPHPerやからあんまり書けんのよね…。
むか~し、簡単な社内アプリをpythonで書いたくらい…。
まぁええか。
とりあえず、これをPHPで書き替えてみよ。(謎)
環境はPHP82、フレームワークはlaravel11を使用している。
まずは日時データから対数収益率とリターン・リスク(標準偏差)を求めるみたい。
対数収益率ってなんやねん。ちょっとググってみよ。
細かいことはわからんが、要は価格のlogと前日の価格のlogを引けばええんやな。
まぁこの実装は簡単やね。
GenerateMSimulation.php$tmp = explode("\n",File::get($this->argument('file'))); foreach ( $tmp as $rec ) { if ( $rec ) { $list = explode("\t",$rec); $value_history[] = $list[1]; } } $log_assets = []; for($i=1;$i<count($value_history);$i++) { $log_assets[] = log($value_history[$i]) - log($value_history[$i-1]); }
引数でファイルを取得してその2カラム目をとってそれをループさせて計算をする。
まぁこんな感じやろ。
次は正規分布N(μ,σ)に従う乱数行列を作成か。
正規分布か~(遠い目)。わからんな。ググるか。
お~。ありがたいことにソース書いてくれている。拝借させていただきます。
GenerateMSimulation.phpprotected function normal($av, $sd){ $x=mt_rand()/mt_getrandmax(); $y=mt_rand()/mt_getrandmax(); return sqrt(-2*log($x))*cos(2*pi()*$y)*$sd+$av; }
PHPでは行列を扱う(使えそうな)ライブラリ等はなかったので、配列で頑張っているな。
よっしゃ。
乱数行列を作成やで。
GenerateMSimulation.phpprotected function run_mc() { $list = []; for ($i=0;$i<$this->period;$i++ ) { $list[$i] = []; for ($j=0;$j<$this->n;$j++ ) { $list[$i][$j] = $this->normal($this->mu,$this->sd); } } :
次は「次元を拡張し、[(投資期間_月),(投資期間_月),(試行回数)]の行列とする」か。
さっぱりわからん。というか、この人ちゃんとコメント入れていてえらいなぁ。
まじ感謝!
…で、np.tileという関数を使っているみたいやなぁ。
np.tileをググってみるかぁ。
なるほど、行列の同じ値を行や列で繰り返す関数ってことかなぁ。
この関数は3次元配列以上も対応しているんだろうが、
今回は2次元配列で十分だからこんな感じかな。
GenerateMSimulation.phpprotected function tile($array,$n,$x,$y) { $tmp1 = []; for($i=0;$i<count($array);$i++) { $tmp1[] = []; for($j1=0;$j1<$x;$j1++) { for($j=0;$j<count($array[$i]);$j++) { $tmp1[$i][] = $array[$i][$j]; } } } $tmp2 = []; for($i1=0;$i1<$y;$i1++) { for($i=0;$i<count($array);$i++) { $tmp2[] = $tmp1[$i]; } } $tmp3 = []; for( $i=0;$i<$n;$i++) { $tmp3[] = $tmp2; } return $tmp3; }
forのオンパレード…。我ながらダサいなぁ。
行列を転置し、[(試行回数),(投資期間_月),(投資期間_月)]の行列とする…か。
とりあえず、ググる。
numpyというのは多次元配列操作に長けてるんやね。
なるほど、なるほど。今更だなぁ。
で、軸の順番を入れ替えるのかぁ。え~。どうやるんやろ。
・
・
・
引数にx軸、y軸、z軸をどこにいれるか(今回は$x=2,$y=0,$z=1)を入れさせておいてベースとなる3次元配列($array)を$i,$j,$kでそれぞれループする。
で、その$i,$j,$kを一旦キー配列($keys)に入れる。
3次元配列の$array[$i][$j][$k]を空の3次元配列($tmp2)の1次元を$keysの$x番目,2次元を$keysの$y番目,3次元を$keysの$z番目とした場所に格納する。
ただし、空の3次元はばらばらの順番で格納されるので一度初期化が必要と。
こんな感じ。
GenerateMSimulation.phpprotected function transpose3d($array,$x=null,$y=null,$z=null) { if ( !(is_null($x) === false && in_array($x,[0,1,2]) == true) ) { $x = 0; } if ( !(is_null($y) === false && in_array($y,[0,1,2]) == true && $x !== $y) ) { $y = 1; } if ( !(is_null($z) === false && in_array($z,[0,1,2]) == true && in_array($z,[$x,$y]) === false ) ) { $z = 2; } //print $x . "\t" . $y . "\t" . $z . "\n"; $tmp2 = []; $m_x = 0; $m_y = 0; $m_z = 0; #各行の最大値を取得 for($i=0;$i<count($array);$i++) { for($j=0;$j<count($array[$i]);$j++) { for($k=0;$k<count($array[$i][$j]);$k++) { $keys = [$i,$j,$k]; $m_x = $keys[$x] > $m_x ? $keys[$x] : $m_x; $m_y = $keys[$y] > $m_y ? $keys[$y] : $m_y; $m_z = $keys[$z] > $m_z ? $keys[$z] : $m_z; } } } #3次元配列を初期化 for($i=0;$i<=$m_x;$i++) { $tmp2[$i] = []; for($j=0;$j<=$m_y;$j++) { $tmp2[$i][$j] = []; for($k=0;$k<=$m_z;$k++) { $tmp2[$i][$j][$k] = null; } } } #3次元配列を次元入れ替え for($i=0;$i<count($array);$i++) { for($j=0;$j<count($array[$i]);$j++) { for($k=0;$k<count($array[$i][$j]);$k++) { $keys = [$i,$j,$k]; $tmp2[$keys[$x]][$keys[$y]][$keys[$z]] = $array[$i][$j][$k]; } } } return $tmp2; }
なんか雑やなぁ。
これを考えるのに数時間かかってる。マジ、雑魚w
次は各試行の上三角行列を取得する。
ググる。
なるほど。これは簡単。
GenerateMSimulation.phpprotected function triu($array) { $tmp = $array; for($i=0;$i<count($array);$i++) { for($j=0;$j<count($array[$i]);$j++) { for($k=0;$k<count($array[$i][$j]);$k++) { if ( $j > $k ) { $tmp[$i][$j][$k] = 0; } } } } return $tmp; }
もっとシンプルな書き方あるんやろなぁ。
これでやっと乱数行列が完成か。次は推移を計算だな。
まずは投資額のデータをつくるんやな。
GenerateMSimulation.phpprotected function exercise($initial,$investment) { $x = []; for($i=0;$i<$this->period;$i++) { $x[] = $investment; } $x[0] += $initial;
総資産額の推移を1年毎(12ヶ月毎)に集計しているな。
pythonの行列はaxis指定で一気に計算できるけど、
phpはそんな芸当できそうにないから普通にループ増やすで。
GenerateMSimulation.phpfor($i=11;$i<$this->period;$i+=12) { $tas = []; $caps = []; #試行ごとにループ for($n=0;$n<$this->n;$n++) { $a = $this->a[$n]; $a_tmp = []; #集計時点までの対数収益率行列を切り出し for($j=0;$j<$i+1;$j++) { $a_tmp[$j] = []; for($k=0;$k<$i+1;$k++) { $a_tmp[$j][$k] = $a[$j][$k]; } } $a_tmp2 = []; #対数収益率行列の各行の和を計算&価格変動比へ変換 for($j=0;$j<count($a_tmp);$j++) { $a_tmp2[] = exp(array_sum($a_tmp[$j])); } $ta = 0; $cap = 0; #各試行の、投資期間経過後の資産額を計算 for($j=0;$j<count($a_tmp2);$j++) { $cap += $x[$j]; $ta += $x[$j] * $a_tmp2[$j]; } $tas[] = $ta; $caps[] = $cap; } $capital[] = $caps; $ta_list[] = $tas; }
np.expやnp.dotもググって内容確認したうえで実装したみたい。
あと、行列の切り出し方も調べてたな。pythonまじ便利。
さぁいよいよ統計計算だな。
…パーセンタイル?
いや、phpで統計計算なんてできるんやろうか?
・
・
見つけた。
すげー。
とりあえず、入れてみるか。
composer require hi-folks/statistics
で、えーとthirdQuartileとfirstQuartileが75%と25%か。
meanが平均だから50%やね。。
10%と90%がないなぁ。
あぁ、quantilesで100分割すればええんやな。
GenerateMSimulation.php$mean = []; $p10 = []; $p25 = []; $p50 = []; $p75 = []; $p90 = []; $years = []; for($i=0;$i<count($ta_list);$i++) { $mean[] = array_sum($ta_list[$i])/count($ta_list[$i]); $quantiles = Stat::quantiles($ta_list[$i],100); $p10[] = $quantiles[10]; $p75[] = $quantiles[75]; $p50[] = $quantiles[50]; $p25[] = $quantiles[25]; $p90[] = $quantiles[90]; $years[] = ($i+1) . '年目'; } print_r($p90); print_r($p75); print_r($p50); print_r($p25); print_r($p10);
とりあえず、実行してみるかぁ。試行回数は10000回、20年でやってみる。
・
・
・
あ、落ちた。メモリが足らんのかini_setでmemory_limit増やしてみるかぁ。
・
・
あかん、2GBつかっても落ちる。やっぱり試行回数分が多すぎかぁ。試行回数でループした方がええなぁ。
ということで作り直し。
・
・
できた。実行する。
・
・
・
重くね。時間計ってみるか。
console# time php artisan generate:ms ./9I311179.txt 0.019 real 8m44.788s user 8m44.313s sys 0m0.360s
あかんね。
ちょっと厳しいなぁ。
ちなみにpythonだとどれくらいなんやろ。
pythonのプログラム落として、ちょっとデータを引数でとれるようにいじって。
・
・
できた。実行してみよ。
console# time python3 asset_model.py ./9I311179.txt real 0m10.819s user 0m27.443s sys 0m4.838s
はやーい。
まじか。
あんまり処理速度考えていない実装やけど、ここまで違うんか。
結論。
『シミュレーションはpythonでやれ』。
一応、今回作成したプログラムを置いておきます。
Laravel11のArtisanコマンドで動作するので
Laravel11のフレームワークを使用して実行してください。