スポンサーリンク
※サイト運営にサーバーは必須です※
~ ロリポップ! はコスパのよい初心者向けサーバーです~
目次
はじめに
※目次用の記事:ギャンブルの賭け方の種類をまとめてみた
モンテカルロ法と聞くと、多くの方は、乱数を用いて数値解析する手法としてのモンテカルロシミュレーションをイメージされると思う。
例えば、1×1の正方形の中に、ランダムで乱数を生み出し、生み出したランダムな点と原点の距離が1以下かどうでないかで、円周率πを乱数を利用して求めるようなものが代表的だろう。
しかし、この記事で紹介するのは、そのモンテカルロシミュレーションではない。
この記事で扱うのは、ギャンブルの領域において、利用される賭け方の1つであるモンテカルロ法だ。
モンテカルロ法とはどのような賭け方であるか説明した後、c++でシミュレーションするというのが目標だ。
計算手法としてのモンテカルロシミュレーションを直接は取り扱ってはいない。
※モンテカルロ法(賭け方)は、モンテカルロシミュレーション(計算手法)使った賭け方を意味しているのではない。
※この記事では、「個別シミュレーション」と「期待値シミュレーション」という二種類のプログラムを紹介する。「期待値シミュレーション」は「個別シミュレーション」を何度も走らせて期待値を求めるようなプログラムであり、乱数を使用して、何度も試行して結果を出しているという点では、計算手法としてのモンテカルロシミュレーションを利用しているといえる。(上の※でも書いたが、モンテカルロ法(賭け方)とモンテカルロシミュレーションは直接的な関係はない)
モンテカルロシミュレーション(計算手法)とモンテカルロ法(賭け方)の名前の出どころはおそらく同じ。カジノで有名な国家モナコ公国の4つの地区の1つであるモンテカルロから由来していると思われる。
モンテカルロ法(賭け方)は、モンテカルロにあるカジノを潰したという逸話があるが、後で紹介するシミュレーション結果から考えると、おそらく眉唾だろう。
モンテカルロ法とは
初めに(1,2,3)という数列を準備する。
数列の左端と右端を足し合わせた数だけ賭ける(最初は1+3=4)
負けた場合:賭け金額を数列の右端に加える
勝った場合:数列の右端と左端を消す
※払戻金の倍率によって、どれだけ数字を消すか変わる
(払戻金が2倍の場合):一番左と一番右。計2つ
例:2連敗後、勝利
(1,2,3,4,5)→(1,2,3,4,5)
(払戻金が3倍の場合):一番左の数字2つと一番右の数字2つ。計4つ
例:2連敗後、勝利
(1,2,3,4,5)→(1,2,3,4,5)
数列の大きさが1以下になった場合は、ゲームをリセット。数列は(1,2,3)から再びスタートする。
※数列の大きさが1以下だと、右端と左端を足し合わせて、賭け金額を算出するという操作ができなくなる。
※主に、払戻金が3倍のパターンで、モンテカルロ法は使用される。
モンテカルロ法の具体例
ルールが非常にわかりにくいので、具体例を挙げて説明する
(払戻金が2倍の場合)
勝負 | 数列 | 賭け金額 | 勝敗 | 利益 |
初期 | 123 | 4 | × | -4 |
× | 1234 | 5 | × | -9 |
×× | 12345 | 6 | ○ | -3 |
××○ | 234 | 6 | × | -9 |
××○× | 2346 | 8 | ○ | -1 |
××○×○ | 34 | 7 | ○ | 6 |
××○×○○ | なし | ゲーム終了 |
(払戻金が3倍の場合)
勝負 | 数列 | 賭け金額 | 勝敗 | 利益 |
初期 | 123 | 4 | × | -4 |
× | 1234 | 5 | × | -9 |
×× | 12345 | 6 | × | -15 |
××× | 123456 | 7 | × | -22 |
×××× | 1234567 | 8 | ○ | -6 |
××××○ | 345 | 8 | ○ | 10 |
××××○○ | なし | ゲーム終了 |
数列の大きさを考察
数列の大きさの動きを考える
払い戻しが2倍の時
負け:+1
勝ち:-2(一番左と一番右。計2つ)
勝率が1/2なら、期待値的に、一回当たり数列の大きさは1/2減る。
+1×(1/2)+(-2)×(1/2)=-1/2
もしも勝率が1/3にこれを使用すると、いつまでたっても、数列が小さくならないということが起こりうる。
+1×(1/3)+(-2)×(2/3)=0
そのため、勝率が1/3を超えるゲームに使用する必要がある。
払い戻しが3倍の時
負け:+1
勝ち:-4(一番左と一番右。計4つ)
勝率が1/3なら、期待値的に、一回当たり数列の大きさは2/3減る。
+1×(2/3)+(-4)×(1/3)=-2/3
もしも勝率が1/4にこれを使用すると、いつまでたっても、数列が小さくならないということが起こりうる。
+1×(3/4)+(-4)×(3/4)=0
そのため、勝率が1/4を超えるゲームに使用する必要がある。
モンテカルロ法の発想
モンテカルロ法が歴史的にどのような発想の下で生まれたかはわからない。
ただ、私は以下のような意図があるのではないかと思う。
まず、負けた場合、賭け金額を右端に加える。これは、負けた金額を記憶しておくことに対応する。
勝った場合、右端と左端を消す。これは、負けた時の金額を帳消しすることを意味する。
ゲームがリセットされ、数列が全部消えた時には、負けた時の金額はだいたい帳消しできている。
例えば、払い戻しが2倍のゲームで、数列が全部消えてゲームが終了した場合、初めの数列である(1,2,3)の合わせて+6だけ手持ちの金額が増える。
しかし、既に気づいている方もいると思うが、このモンテカルロ法は、数列1つ数字が残っている場合もゲームがリセットされる。
その場合、
6-(最後に残った数列の数字)
が手持ちの金額として増減する。
もしも(最後に残った数列の数字)が大きいと、ゲームがリセットされても損することがある
※払い戻し金が3倍の場合は、数列を一気に4つ消すが、この消して数列と勝利して得られる金額が必ずしも一致するわけではないので、払い戻し金が2倍の場合のような単純な式で表せない。
※もしも、ゲームがリセットされても、損することのない手法にしたいのなら、数列の大きさが1になっても、一番左に、新たに数字に1を加えて、ゲームを続行させるルールを加えれば可能である。例えば、最後の数列の数字が(13)だったら、左に1を加え、(1,13)にしてゲームを続けるようにする。
※モンテカルロ法のマインドとしては、マーチンゲール法のように一度の勝ちで今までの負債を返すのではなく、何度か勝負を繰り返して、負債を打ち消していく手法といえる。
※一部のサイトで、モンテカルロ法の数列の増え方が異なるものがある。例えば、(4,5,6)の数列の状況で負けた時、上で説明したルールに従うなら4+6=10を右端に加えた(4,5,6,10)となる。しかし、いくつかのサイトでは、右端の数列は単純に増えればいいと考え、6の次の7を加えて(4,5,6,7)になると紹介している。もしも、今までの負債を打ち消したいのなら、この手法ではつじつまが合わなくなる。モンテカルロ法での数列の増え方は、(4,5,6,10)になるような流儀の方が正しいと私は考えている。
モンテカルロ法の検証
以下では2種類のプログラムのを紹介する。
それぞれの種類に対して、払い戻し2倍と3倍のバージョンを乗せ、計4つ掲載する。
言語はC++で作った。
※C言語では動かない。大きさの変わる配列を扱う必要があり、vectorという動的配列を取り扱っている。vector はC言語では使用できないので注意。
※C言語でも、動的配列を取り扱う方法はあるはずだが、私はC言語で動的配列を扱ったことがないので、詳しくは知らない
※数列に数字を加えたり、消したりするところに動的配列を利用している。ぶっちゃけ、この機能がきちんと動くか確認して実装するのは、めんどくさかった。
※このプログラムを組んだ人間は、大学時代に少しプログラムをかじった程度の戦闘能力しかない。
モンテカルロ法個別シミュレーション
払い戻し2倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <vector> using namespace std; // 名前空間指定 int main() { int i=0; int i_max=100000;//勝負回数 double w=2.0; //賭け金の変換率(2倍以上)(win) double p=50; //勝率(50%以下)(probability) double m_min=1;//最小の賭け金額(money) double m_ini=1000;//最初の手持ちの資金(money_initial) int c;//勝ち負け差のカウント(count) c=0;//初期化 //乱数(時間による変化) srand((unsigned)time(NULL)); //ファイルの出力準備 FILE *sf; sf = fopen("simulation.dat","w");//出力するファイルの名前を指定 //エラー if(sf==NULL){ printf("ファイルオープンエラー\n"); return -1; } //初期条件の表示 printf("#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); fprintf(sf,"#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); double m_now;//現在の資金 double m_bet;//賭け金 //初期化 m_now=m_ini;//最初は最初の手持ちの資金でスタート //m_bet=m_min;//賭け金は最小の賭け金額でスタート // vct(vector)を生成 int n; vector<int> vec; //初期化 for(n=1;n<=3;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min; for(i=1;i<=i_max;i++) { double r;//乱数(rand) r=100.0*rand()/(RAND_MAX+1.0);//0から100の乱数を出す printf("%d回目\t賭け金は%lf\t",i,m_bet); fprintf(sf,"%d回目\t賭け金は%lf\t",i,m_bet); printf("数列:");//配列の出力 fprintf(sf,"数列:"); for(n=0;n<size;n++){ printf("%d/",vec[n]); fprintf(sf,"%d/",vec[n]); } printf("\t"); fprintf(sf,"\t"); if (p<=r){//負けの場合 m_now-=m_bet; vec.push_back(m_bet/m_min); // 末尾にm_bet を追加 c-=1; printf("lose\t"); fprintf(sf,"lose\t"); } else{//勝ちの場合 m_now +=m_bet*(w-1.0); vec.erase(vec.begin()); vec.pop_back(); c+=1; printf("win\t"); fprintf(sf,"win\t"); } printf("勝敗差%d\t手持ちのお金は%lf",c,m_now); fprintf(sf,"勝敗差%d\t手持ちのお金は%lf",c,m_now); size = (int)vec.size();//sizeの大きさ再度確認 if(size<=1){ printf("\t数列リセット"); fprintf(sf,"\t数列リセット"); vec.clear(); for(int n=1;n<=3;n++) { vec.push_back(n); } size = (int)vec.size();//sizeの大きさ再度確認 } printf("\n"); fprintf(sf,"\n"); m_bet=(vec[0]+vec[size-1])*m_min; if(m_now<m_bet){//手持ちの金より賭けるお金が高い場合 printf("%d回目で手持ちのお金が足りません\n",i); fprintf(sf,"%d回目で手持ちのお金が足りません\n",i); break; } }//for(i)文の終わり //sfクローズ fclose(sf); return 0; }//プログラムの終わり |
払い戻し3倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <vector> using namespace std; // 名前空間指定 int main() { int i=0; int i_max=100000;//勝負回数 double w=3.0; //賭け金の変換率(2倍以上)(win) double p=33.333; //勝率(50%以下)(probability) double m_min=1;//最小の賭け金額(money) double m_ini=1000;//最初の手持ちの資金(money_initial) int c;//勝ち負け差のカウント(count) c=0;//初期化 //乱数(時間による変化) srand((unsigned)time(NULL)); //ファイルの出力準備 FILE *sf; sf = fopen("simulation.dat","w");//出力するファイルの名前を指定 //エラー if(sf==NULL){ printf("ファイルオープンエラー\n"); return -1; } //初期条件の表示 printf("#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); fprintf(sf,"#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); double m_now;//現在の資金 double m_bet;//賭け金 //初期化 m_now=m_ini;//最初は最初の手持ちの資金でスタート //m_bet=m_min;//賭け金は最小の賭け金額でスタート // vct(vector)を生成 int n; vector<int> vec; //初期化 for(n=1;n<=3;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min; for(i=1;i<=i_max;i++) { double r;//乱数(rand) r=100.0*rand()/(RAND_MAX+1.0);//0から100の乱数を出す printf("%d回目\t賭け金は%lf\t",i,m_bet); fprintf(sf,"%d回目\t賭け金は%lf\t",i,m_bet); printf("数列:");//配列の出力 fprintf(sf,"数列:"); for(n=0;n<size;n++){ printf("%d/",vec[n]); fprintf(sf,"%d/",vec[n]); } printf("\t"); fprintf(sf,"\t"); if (p<=r){//負けの場合 m_now-=m_bet; vec.push_back(m_bet/m_min); // 末尾にm_bet を追加 c-=1; printf("lose\t"); fprintf(sf,"lose\t"); } else{//勝ちの場合 m_now +=m_bet*(w-1.0); vec.erase(vec.begin()); vec.erase(vec.begin()); vec.pop_back(); vec.pop_back(); c+=1; printf("win\t"); fprintf(sf,"win\t"); } printf("勝敗差%d\t手持ちのお金は%lf",c,m_now); fprintf(sf,"勝敗差%d\t手持ちのお金は%lf",c,m_now); size = (int)vec.size();//sizeの大きさ再度確認 if(size<=1){ printf("\t数列リセット"); fprintf(sf,"\t数列リセット"); vec.clear(); for(int n=1;n<=3;n++) { vec.push_back(n); } size = (int)vec.size();//sizeの大きさ再度確認 } printf("\n"); fprintf(sf,"\n"); m_bet=(vec[0]+vec[size-1])*m_min; if(m_now<m_bet){//手持ちの金より賭けるお金が高い場合 printf("%d回目で手持ちのお金が足りません\n",i); fprintf(sf,"%d回目で手持ちのお金が足りません\n",i); break; } }//for(i)文の終わり //sfクローズ fclose(sf); return 0; }//プログラムの終わり |
プログラムで使用している変数の説明
※マーチンゲール法(2倍賭け)の破綻までのシミュレーションで紹介したプログラムと同じ変数の置き方をしている
i_max:勝負の回数
w:賭けたお金の戻る倍率を指定。ここでは2倍(3倍)を指定
p:勝率を指定。ここでは1/2(1/3)の確率なので50(33.333)を指定。
m_min:かけ金を指定
m_ini:最初の資金を指定
※ここではm_min=1でm_ini=1000と、最小の賭け金の1000倍を所持していると考えている。
例えば、最小の賭け金が1000円(千円)だとするなら、手元の軍資金は、1000000円(百万円)。割と現実的な設定だと思われる。
r:乱数(0~100の乱数)
※乱数rが勝率pより下の数で収まるなら勝ちの判定がでる。
そして、勝負を続けていくうちに、負け続けることもあるだろう。
そしてついに、賭けしようにも手持ちのお金が足りなくなるかもしれない。
この場合、これ以上勝負ができなくなり、プログラムは終了する。(破綻判定)
つまり、借金はNG
c:トータルの勝ち負けの差がどのくらいあるか調べる用 。
払い戻し2倍と払い戻し3倍のプログラムの違い
- 賭けたお金の戻る倍率wと勝率pの値の違い
- 数列の先頭と最後尾を消すための記述が3倍の場合2回使用。
(下の部分)
1 2 |
vec.erase(vec.begin()); vec.pop_back(); |
実際にプログラムを走らせると以下のような結果となる
勝負回数i_maxは100000(10万回)までとする
※最初の賭け金額は1+3=4となる
(2倍のケース)
#倍率は2.000000 勝率は50.000000 最小の賭け金は1.000000 手持ちのお金は1000.000000
1回目 賭け金は4.000000 数列:1/2/3/ lose 勝敗差-1 手持ちのお金は996.000000
2回目 賭け金は5.000000 数列:1/2/3/4/ win 勝敗差0 手持ちのお金は1001.000000
3回目 賭け金は5.000000 数列:2/3/ win 勝敗差1 手持ちのお金は1006.000000 数列リセット
4回目 賭け金は4.000000 数列:1/2/3/ lose 勝敗差0 手持ちのお金は1002.000000
(省略)
12044回目 賭け金は1158.000000 数列:184/258/332/406/480/554/628/790/974/ lose 勝敗差-230 手持ちのお金は1569.000000
12045回目 賭け金は1342.000000 数列:184/258/332/406/480/554/628/790/974/1158/ lose 勝敗差-231 手持ちのお金は227.000000
12045回目で手持ちのお金が足りません
(3倍のケース)
#倍率は3.000000 勝率は33.333000 最小の賭け金は1.000000 手持ちのお金は1000.000000
1回目 賭け金は4.000000 数列:1/2/3/ lose 勝敗差-1 手持ちのお金は996.000000
2回目 賭け金は5.000000 数列:1/2/3/4/ lose 勝敗差-2 手持ちのお金は991.000000
3回目 賭け金は6.000000 数列:1/2/3/4/5/ lose 勝敗差-3 手持ちのお金は985.000000
4回目 賭け金は7.000000 数列:1/2/3/4/5/6/ lose 勝敗差-4 手持ちのお金は978.000000
5回目 賭け金は8.000000 数列:1/2/3/4/5/6/7/ lose 勝敗差-5 手持ちのお金は970.000000
6回目 賭け金は9.000000 数列:1/2/3/4/5/6/7/8/ win 勝敗差-4 手持ちのお金は988.000000
7回目 賭け金は9.000000 数列:3/4/5/6/ lose 勝敗差-5 手持ちのお金は979.000000
(省略)
736回目 賭け金は402.000000 数列:45/66/87/132/177/222/267/312/357/ lose 勝敗差-266 手持ちのお金は239.000000
736回目で手持ちのお金が足りません
※srand((unsigned)time(NULL));の部分で時間を参照した上で乱数を発生させている。そのため、実行するタイミングで結果が変化する。
モンテカルロ法にまつわるデマ
一部のサイトで「払戻金3倍の場合は、数列がリセットされると、最初よりも必ず所持金が増える」という主張しているが、これは嘘。
これに気がつかず、モンテカルロ法は必勝法だとトンチンカンなことをいう人がいる。
※紹介のされ方として、払戻金2倍の場合は、数列がリセットされた時、損することはあるが、払戻金3倍の時は必ずうまくいくみたいな書かれ方が多い。
数学的に、「払戻金3倍の場合は、ゲームがリセットされると、最初よりも必ず所持金が増える」が「偽」であることを証明するには、反例を上げればよい。
以下が証拠
個別シミュレーションの一部を切り取っている。
(3倍払い戻しバージョンの結果)
手持ちのお金は1495.000000 数列リセット
283回目 賭け金は4.000000 数列:1/2/3/ lose 勝敗差-87 手持ちのお金は1491.000000
284回目 賭け金は5.000000 数列:1/2/3/4/ lose 勝敗差-88 手持ちのお金は1486.000000
285回目 賭け金は6.000000 数列:1/2/3/4/5/ lose 勝敗差-89 手持ちのお金は1480.000000
286回目 賭け金は7.000000 数列:1/2/3/4/5/6/ win 勝敗差-88 手持ちのお金は1494.000000
287回目 賭け金は7.000000 数列:3/4/ lose 勝敗差-89 手持ちのお金は1487.000000
288回目 賭け金は10.000000 数列:3/4/7/ lose 勝敗差-90 手持ちのお金は1477.000000
289回目 賭け金は13.000000 数列:3/4/7/10/ lose 勝敗差-91 手持ちのお金は1464.000000
290回目 賭け金は16.000000 数列:3/4/7/10/13/ lose 勝敗差-92 手持ちのお金は1448.000000
291回目 賭け金は19.000000 数列:3/4/7/10/13/16/ lose 勝敗差-93 手持ちのお金は1429.000000
292回目 賭け金は22.000000 数列:3/4/7/10/13/16/19/ lose 勝敗差-94 手持ちのお金は1407.000000
293回目 賭け金は25.000000 数列:3/4/7/10/13/16/19/22/ win 勝敗差-93 手持ちのお金は1457.000000
294回目 賭け金は23.000000 数列:7/10/13/16/ lose 勝敗差-94 手持ちのお金は1434.000000
295回目 賭け金は30.000000 数列:7/10/13/16/23/ win 勝敗差-93 手持ちのお金は1494.000000 数列リセット
282回目でゲームがリセットされる。
283回目の最初の段階で所持金が1495。
295回目の終了後の所持金は、1494。(最後に数列は7/10/13/16/23/)
ゲームがリセットされたとき、リセット前よりも持っている所持金は1だけ少なくなっている
モンテカルロ法の期待値シミュレーション
上のプログラムを何度も走らせて期待値や破綻する率をもとめたい
※この期待値を求める時にモンテカルロシミュレーションの考え方が使用されている。
そのためのプログラムは以下のようになる
払い戻し2倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <vector> using namespace std; // 名前空間指定 int main() { int k; int k_max=100000;//試行回数 int i=0; int i_max=100000;//勝負回数 double w=2.0; //賭け金の変換率(2倍以上)(win) double p=50; //勝率(50%以下)(probability) double m_min=1;//最小の賭け金額(money) double m_ini=1000;//最初の手持ちの資金(money_initial) int b;//破たん回数のカウント(Bankruptcy) b=0;//初期化 int i_sum;//勝負回数の合計 double m_sum;//最終的なお金の合計 i_sum=0;//初期化 m_sum=0.0; int c_sum;//勝負差の合計 c_sum=0;//初期化 //乱数(時間による変化) srand((unsigned)time(NULL)); //ファイルの出力準備 FILE *sf; sf = fopen("simulation.dat","w");//出力するファイルの名前を指定 //エラー if(sf==NULL){ printf("ファイルオープンエラー\n"); return -1; } //初期条件の表示 printf("#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); fprintf(sf,"#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); for(k=1;k<=k_max;k++) { int i_count;//勝負回数のカウント i_count=0; double m_now;//現在の資金 double m_bet;//賭け金 //初期化 m_now=m_ini;//最初は最初の手持ちの資金でスタート int c;//勝ち負け差のカウント(count) c=0;//初期化 // vct(vector)を生成 int n; vector<int> vec; //初期化 for(n=1;n<=3;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min;//賭け金 for(i=1;i<=i_max;i++) { double r;//乱数(rand) r=100.0*rand()/(RAND_MAX+1.0);//0から100の乱数を出す if (p<=r){//負けの場合 m_now-=m_bet; vec.push_back(m_bet/m_min); // 末尾にm_bet を追加 c-=1; } else{//勝ちの場合 m_now +=m_bet*(w-1.0); vec.erase(vec.begin()); vec.pop_back(); c+=1; } i_count=i;//i_countを定義しないと破たんしない(セーフ)場合、iと値が1ずれてしまうため size = (int)vec.size();//sizeの大きさ再度確認 if(size<=1){ vec.clear(); for(int n=1;n<=3;n++) { vec.push_back(n); } size = (int)vec.size();//sizeの大きさ再度確認 } m_bet=(vec[0]+vec[size-1])*m_min; if(m_now<m_bet){//手持ちの金より賭けるお金が高い場合 break; } }//for(i)文の終わり if(i_count<i_max) { printf("%d番目%d回目で破たん\t手持ちのお金は%lf\n",k,i_count,m_now); fprintf(sf,"%d番目%d回目で破たん\t手持ちのお金は%lf\n",k,i_count,m_now); b=b+1; } else{ printf("%d番目\t%d回目までセーフ\t手持ちのお金は%lf\n",k,i_count,m_now); fprintf(sf,"%d番目\t%d回目までセーフ\t手持ちのお金は%lf\n",k,i_count,m_now); } i_sum=i_sum+i_count; m_sum=m_sum+m_now; c_sum=c_sum+c; }//for(k)文の終わり //破たん率 printf("勝負回数は%d\n",i_max); printf("試行回数は%d\n",k_max); printf("破たん回数は%d\n",b); fprintf(sf,"勝負回数は%d\n",i_max); fprintf(sf,"試行回数は%d\n",k_max); fprintf(sf,"破たん回数は%d\n",b); double b_pro= (double)b/(double)k_max*100.0;//(double)を抜くとb/k_maxがゼロと評価されてしまう printf("破たん率は%lf%\n",b_pro); fprintf(sf,"破たん率は%lf%\n",b_pro); double i_exp=(double)i_sum/(double)k_max; double m_exp=(double)m_sum/(double)k_max; printf("破たんするまでに行える勝負回数の期待値(i_exp)は%lf\n",i_exp); printf("破たんする直前で持っているお金の期待値(m_exp)は%lf\n",m_exp); fprintf(sf,"破たんするまでに行える勝負回数の期待値(i_exp)は%lf\n",i_exp); fprintf(sf,"破たんする直前で持っているお金の期待値(m_exp)は%lf\n",m_exp); double c_exp=(double)c_sum/(double)k_max; printf("破たんした時の勝負差の期待値は%lf\n",c_exp); fprintf(sf,"破たんした時の勝負差の期待値は%lf\n",c_exp); //sfクローズ fclose(sf); return 0; }//プログラムの終わり |
払い戻し3倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <vector> using namespace std; // 名前空間指定 int main() { int k; int k_max=100000;//試行回数 int i=0; int i_max=100000;//勝負回数 double w=3.0; //賭け金の変換率(2倍以上)(win) double p=33.333; //勝率(50%以下)(probability) double m_min=1;//最小の賭け金額(money) double m_ini=1000;//最初の手持ちの資金(money_initial) int b;//破たん回数のカウント(Bankruptcy) b=0;//初期化 int i_sum;//勝負回数の合計 double m_sum;//最終的なお金の合計 i_sum=0;//初期化 m_sum=0.0; int c_sum;//勝負差の合計 c_sum=0;//初期化 //乱数(時間による変化) srand((unsigned)time(NULL)); //ファイルの出力準備 FILE *sf; sf = fopen("simulation.dat","w");//出力するファイルの名前を指定 //エラー if(sf==NULL){ printf("ファイルオープンエラー\n"); return -1; } //初期条件の表示 printf("#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); fprintf(sf,"#倍率は%lf\t勝率は%lf\t最小の賭け金は%lf\t手持ちのお金は%lf\n",w,p,m_min,m_ini); for(k=1;k<=k_max;k++) { int i_count;//勝負回数のカウント i_count=0; double m_now;//現在の資金 double m_bet;//賭け金 //初期化 m_now=m_ini;//最初は最初の手持ちの資金でスタート int c;//勝ち負け差のカウント(count) c=0;//初期化 // vct(vector)を生成 int n; vector<int> vec; //初期化 for(n=1;n<=3;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min;//賭け金 for(i=1;i<=i_max;i++) { double r;//乱数(rand) r=100.0*rand()/(RAND_MAX+1.0);//0から100の乱数を出す if (p<=r){//負けの場合 m_now-=m_bet; vec.push_back(m_bet/m_min); // 末尾にm_bet を追加 c-=1; } else{//勝ちの場合 m_now +=m_bet*(w-1.0); vec.erase(vec.begin()); vec.erase(vec.begin()); vec.pop_back(); vec.pop_back(); c+=1; } i_count=i;//i_countを定義しないと破たんしない(セーフ)場合、iと値が1ずれてしまうため size = (int)vec.size();//sizeの大きさ再度確認 if(size<=1){ vec.clear(); for(int n=1;n<=3;n++) { vec.push_back(n); } size = (int)vec.size();//sizeの大きさ再度確認 } m_bet=(vec[0]+vec[size-1])*m_min; if(m_now<m_bet){//手持ちの金より賭けるお金が高い場合 break; } }//for(i)文の終わり if(i_count<i_max) { printf("%d番目%d回目で破たん\t手持ちのお金は%lf\n",k,i_count,m_now); fprintf(sf,"%d番目%d回目で破たん\t手持ちのお金は%lf\n",k,i_count,m_now); b=b+1; } else{ printf("%d番目\t%d回目までセーフ\t手持ちのお金は%lf\n",k,i_count,m_now); fprintf(sf,"%d番目\t%d回目までセーフ\t手持ちのお金は%lf\n",k,i_count,m_now); } i_sum=i_sum+i_count; m_sum=m_sum+m_now; c_sum=c_sum+c; }//for(k)文の終わり //破たん率 printf("勝負回数は%d\n",i_max); printf("試行回数は%d\n",k_max); printf("破たん回数は%d\n",b); fprintf(sf,"勝負回数は%d\n",i_max); fprintf(sf,"試行回数は%d\n",k_max); fprintf(sf,"破たん回数は%d\n",b); double b_pro= (double)b/(double)k_max*100.0;//(double)を抜くとb/k_maxがゼロと評価されてしまう printf("破たん率は%lf%\n",b_pro); fprintf(sf,"破たん率は%lf%\n",b_pro); double i_exp=(double)i_sum/(double)k_max; double m_exp=(double)m_sum/(double)k_max; printf("破たんするまでに行える勝負回数の期待値(i_exp)は%lf\n",i_exp); printf("破たんする直前で持っているお金の期待値(m_exp)は%lf\n",m_exp); fprintf(sf,"破たんするまでに行える勝負回数の期待値(i_exp)は%lf\n",i_exp); fprintf(sf,"破たんする直前で持っているお金の期待値(m_exp)は%lf\n",m_exp); double c_exp=(double)c_sum/(double)k_max; printf("破たんした時の勝負差の期待値は%lf\n",c_exp); fprintf(sf,"破たんした時の勝負差の期待値は%lf\n",c_exp); //sfクローズ fclose(sf); return 0; }//プログラムの終わり |
プログラムで使用している変数の説明
b_pro:破綻率
※ここでいう破綻率とは手持ちのお金が、賭けに耐えられない状況を意味する。借金してお金を用意することはできないとする。
i_exp: 破綻するまで何回勝負ができるかの期待値
m_exp: 勝負が終わった段階で持っているお金の期待値
k_max:試行回数。
※k_maxを大きくすればするほど、正確な期待値が求まる。
c_exp:勝敗差の期待値
以下では、k_max=100000、i_max=100000で指定。
試行回数10万回、勝負回数10万回。
結果は以下のようになる
(払い戻し2倍)
勝負回数は100000
試行回数は100000
破たん回数は99051
破たん率は99.051000%
破たんするまでに行える勝負回数の期待値(i_exp)は4858.110280
破たんする直前で持っているお金の期待値(m_exp)は991.582270
破たんした時の勝負差の期待値は0.375980
(払い戻し3倍)
勝負回数は100000
試行回数は100000
破たん回数は99806
破たん率は99.806000%
破たんするまでに行える勝負回数の期待値(i_exp)は1880.049330
破たんする直前で持っているお金の期待値(m_exp)は996.400290
破たんした時の勝負差の期待値は-626.707670
破綻率は、払い戻し2倍の場合は、99.05%、払い戻し3倍の場合は、99.806%。払い戻し2倍の方が破綻する率はわずかに低い。両方とも99%を超えて、差は約0.75%だ。
勝負回数の期待値(i_exp)において、払い戻し2倍(4858回)の方が、払い戻し3倍(1880回)と比べて2倍以上大きいことから、破綻率の差は誤差ではなく、有意な差である。
この差が何に由来するかはきちんとは説明できないが、今まで(グラン)マーチンゲールやココモ法などのシミュレーションをしてきた経験から思ったことが一つある。
局所的に見て、今まで負けてきた分の損失をリセットできる賭けの手法は、破綻率が高くでる。そして、負けてきた分の損失をリセットするだけでなく、利益を出すような手法だとさらに破綻率は高くでる傾向にある。
モンテカルロ法において、払い戻し2倍と3倍の手法はどちらも、毎回、今まで負けてきた分の損失をリセットできるわけではない。だが、かなりの高確率で、リセットできる手法ではある。
払い戻し2倍と3倍で、どちらの方が、今まで負けてきた分の損失をリセットでき、その上で利益を出せるかと考えてみた。何度か個別シミュレーションを走らせてみた結果、どうにも払い戻し3倍の方が、損失をリセットできる手法だとわかってきた。
今までの損失を返す力が、払い戻し3倍の手法の方が高いので、等価交換(?)として、破綻率も高くなるのだろう。
まとめ
モンテカルロ法にまつわる以下の噂は虚偽である
- モンテカルロ法は必勝法である
→期待値シミュレーションでお金が増えていないことから、モンテカルロ法を使っても、手元のお金は増えない - モンテカルロ法は数列がリセットされた時、収支が必ず+になる
→数列がリセットされた時、必ず、最初より手持ちのお金が増えているとは限らない
書籍の紹介
『ギャンブルの必勝法が本当に儲かるかプログラミングで検証してみた』は以下の疑問にお答えします。
●勝率が50%の場合、利益を生み出す必勝法は存在するか?
●勝率が60%の場合、どのように賭けるのが最適か?
必勝法と思われている手法を15種類紹介します。必勝法には、例えば、マーチンゲール法(負けた時に2倍賭ける手法)などがあります。これらの手法が本当に儲かるかプログラミングを使用して検証します。また、検証するために必要なプログラミングの知識(C#)も紹介しています。
ギャンブルの必勝法が本当に儲かるかプログラミングで検証してみた
関連記事
※目次用の記事:ギャンブルの賭け方の種類をまとめてみた
~ギャンブルに絶対儲かる必勝法があるのだろうか?~
私(サイト主)はこの疑問に対して非常に興味を持ち、プログラミングで検証してみました。
このサイトを応援してもいいかなと思う人はぜひとも購入を検討してみてください。
はじめまして、ITOと申します。
モンテカルロ法のベットプログラムを作成したくて
探しておりましたらこのWebサイトに行き当たりました。
Javascriptでプログラムを組みたいのですが、
有償で作成していただけないでしょうか?
もし可能な場合は詳細をお送りいたしますので
お見積り頂けたらと思います。
よろしくお願いいたします。
コメントありがとうございます。
申し訳ございませんが、サイト主はJavascriptの知識はありません。
今回頂いたご依頼は、私では対応致しかねますので、ご容赦ください。
初めまして。試行回数10万回との事ですが、通常人間なら回数云々よりも、オンラインカジノであれば自分の入金額に対してある一定のところで辞めませんか?例えば10万入金して最低bet数の4ドルからモンテカルロ法をやったとして、10万円が1000万、人によっては100万になったら満足してやめると思います。なので回数よりも、10万円が10倍で100万なのでモンテカルロ法を使って10倍になる確率と100倍になる確率と破綻率を調査して欲しいです。
以下の記事を参考にして、ご自身で検証をしてみてください。
マーチンゲール法で2倍儲かる確率を検証(c++/c言語)
確率は高くないと思われます。
見逃していました。ありがとうございます。一番破綻率が低く出たグッドマン系の攻略法も別で調べてもらえると助かります。