
色々あって積立投資シミュレーションが作りたくなったらしい。
とりあえず、ググってみると
が見つかった。 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.php
protected 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.php
protected 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.php
protected 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.php
protected 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.php
protected 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.php
protected function exercise($initial,$investment)
{
$x = [];
for($i=0;$i<$this->period;$i++) {
$x[] = $investment;
}
$x[0] += $initial;
総資産額の推移を1年毎(12ヶ月毎)に集計しているな。
pythonの行列はaxis指定で一気に計算できるけど、
phpはそんな芸当できそうにないから普通にループ増やすで。
GenerateMSimulation.php
for($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のフレームワークを使用して実行してください。