スポンサーリンク
※サイト運営にサーバーは必須です※
~このサイトもエックスサーバーを使用しています~
目次
はじめに
※目次用の記事:ギャンブルの賭け方の種類をまとめてみた
モンテカルロ法と聞くと、多くの方は、乱数を用いて数値解析する手法としてのモンテカルロシミュレーションをイメージされる人は多いと思うが、この記事で扱うのは、ギャンブルの領域において、利用される賭け方の1つであるモンテカルロ法。
※モンテカルロ法(賭け方)は、モンテカルロシミュレーション(計算手法)使った賭け方を意味しているのではない。
この記事では、モンテカルロ法の欠点と、それを改善した手法を提案する。最後に、c++を用いてシミュレーションした結果をのせておく。
※モンテカルロ法(賭け方)を知らない人は、モンテカルロ法の賭けシミュレーション(c++)という記事を参照してください。モンテカルロ法(賭け方)がある程度わからないとこの記事はわかりづらいと思われるので……。
モンテカルロ法とは
一応、モンテカルロ法のルールを確認する。
初めに(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倍のパターンで、モンテカルロ法は使用される。
モンテカルロ法の欠点
- 数列がリセットされた時、必ず、最初よりも手持ちのお金が増えるわけではない
(理由)
数列1つ数字が残っている場合も数列がリセットされるが、その場合、6-(最後に残った数列の数字)が手持ちの金額として増減する。
※6という数字は(1,2,3)の合わせて+6に由来する
もしも(最後に残った数列の数字)が大きいと、ゲームがリセットされても損する。
- 最初に賭ける金額が、1ではなく、4である。
破綻する率を減らしたいなら、ゲームがリセットされた時に、最小単位の賭け金額からスタートできるのが望ましい。しかし、モンテカルロ法は、(1,2,3)という数列で決まるため、最小単位の賭け金額の4倍からスタートせざるを得ない。
※(÷4)すれば、いいという問題ではない。なぜなら、もし負けた場合、次に必要な賭け金額は(1,2,3,4)の数列の両端を足した5となる。5÷4=1.25で、小数となってしまい、困る。
モンテカルロ法の改善提案
- 数列1つ数字が残っている場合は数列をリセットせず、その数字を二つに分解して、ゲームを続行する。
例えば、数列の数字が7だけだった場合、従来のモンテカルロ法はそこでいったん数列をリセットしていたが、ここでは(7)→(3,4)と、なるべく均等になるように分解する
- 最初にスタートする数列を(1,2,3)ではなく、(0,1)でスタートする
そうすることで、最小の賭け単位で勝負ができるようになる。
モンテカルロ法の改良版のルール確認
初めに(0,1)という数列を準備する。
数列の左端と右端を足し合わせた数だけ賭ける(最初は0+1=1)
負けた場合:賭け金額を数列の右端に加える
勝った場合:数列の右端と左端を消す
※勝率によって、どれだけ数字を消すか変わる
(払戻金が2倍の場合):一番左と一番右。計2つ
(払戻金が3倍の場合):一番左の数字2つと一番右の数字2つ。計4つ
数列が完全に消えた場合は、数列をリセット。数列は(0,1)から再びスタートする。
数列が1つ残った場合は、数字をなるべく均等に分解する。数列は、分解した2つの数字を並べた数列を使用する。
例:数字分解時は、小さい数字を左に、大きい数字を右に書く
5→(2,3)
8→(4,4)
11→(5,6)
モンテカルロ法(分解法)の特徴
- 数列がリセットされた時に、最初に持っていた金額よりも+1多い金額になる。
なぜなら、数列が(0,1)からスタートして、右端に賭ける金額の数字を加えることで、負けた時の賭け金額を記憶しておく。数列が全部消えたという状況は、最初の数列の合計である0+1=1と、このセッションで負けた分の賭け金額を回収できたことに他ならない。
- 主に、払い戻し2倍の時に使用
※払い戻し3倍の時は、一番左の数字2つと一番右の数字2つ。計4つ消すので、「数列がリセットされた時に、最初に持っていた金額よりも+1になる」というのは正確には成り立たない。
- 払い戻し2倍の時、局所的に見て、負けた数が勝ちの数が同じくらいであれば、いずれ、数列をリセットされる
※正確には、負けた数が勝ちの数より2倍を超えなければ、期待値的に、数列は小さくなるはず。
マーチンゲール法のように一度の勝ちで、今までの負債を帳消しするのではなく、ゆっくりと、負けた金額を回収するため、資金が尽きるスピードは遅くなることが期待される。
モンテカルロ法(分解法)の検証
以下では2種類のプログラムを紹介する。
それぞれの種類に対して、払い戻し2倍と3倍のバージョンを乗せ、計4つ掲載する。
言語はC++で作った。
※C言語では動かない。大きさの変わる配列を扱う必要があり、vectorという動的配列を取り扱っている。vector は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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
#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=0;n<=1;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min; int m_bet0;//分解用 int m_bet1; 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数字分解"); m_bet=vec[0];//数列の記憶 m_bet0=m_bet/2; m_bet1=m_bet-m_bet0; vec.clear(); vec.push_back(m_bet0); vec.push_back(m_bet1); size = (int)vec.size();//sizeの大きさ再度確認 } if(size<=0){ printf("\t数列リセット"); fprintf(sf,"\t数列リセット"); vec.clear(); for(int n=0;n<=1;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("次の勝負に必要な金額は%lfです。%d回目でゲームを終了します\n",m_bet,i); fprintf(sf,"次の勝負に必要な金額は%lfです。%d回目でゲームを終了します\n",m_bet,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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
#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=0;n<=1;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min; int m_bet0;//分解用 int m_bet1; 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数字分解"); m_bet=vec[0];//数列の記憶 m_bet0=m_bet/2; m_bet1=m_bet-m_bet0; vec.clear(); vec.push_back(m_bet0); vec.push_back(m_bet1); size = (int)vec.size();//sizeの大きさ再度確認 } if(size<=0){ printf("\t数列リセット"); fprintf(sf,"\t数列リセット"); vec.clear(); for(int n=0;n<=1;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("次の勝負に必要な金額は%lfです。%d回目でゲームを終了します\n",m_bet,i); fprintf(sf,"次の勝負に必要な金額は%lfです。%d回目でゲームを終了します\n",m_bet,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万回)までとする
※最初の賭け金額は0+1=1となる
(2倍の払い戻しの場合)
#倍率は2.000000 勝率は50.000000 最小の賭け金は1.000000 手持ちのお金は1000.000000
1回目 賭け金は1.000000 数列:0/1/ win 勝敗差1 手持ちのお金は1001.000000 数列リセット
2回目 賭け金は1.000000 数列:0/1/ lose 勝敗差0 手持ちのお金は1000.000000
3回目 賭け金は1.000000 数列:0/1/1/ win 勝敗差1 手持ちのお金は1001.000000 数字分解
4回目 賭け金は1.000000 数列:0/1/ lose 勝敗差0 手持ちのお金は1000.000000
5回目 賭け金は1.000000 数列:0/1/1/ lose 勝敗差-1 手持ちのお金は999.000000
(省略)
45回目 賭け金は1.000000 数列:0/1/ win 勝敗差-1 手持ちのお金は1007.000000 数列リセット
46回目 賭け金は1.000000 数列:0/1/ lose 勝敗差-2 手持ちのお金は1006.000000
47回目 賭け金は1.000000 数列:0/1/1/ lose 勝敗差-3 手持ちのお金は1005.000000
48回目 賭け金は1.000000 数列:0/1/1/1/ lose 勝敗差-4 手持ちのお金は1004.000000
49回目 賭け金は1.000000 数列:0/1/1/1/1/ win 勝敗差-3 手持ちのお金は1005.000000
50回目 賭け金は2.000000 数列:1/1/1/ lose 勝敗差-4 手持ちのお金は1003.000000
51回目 賭け金は3.000000 数列:1/1/1/2/ lose 勝敗差-5 手持ちのお金は1000.000000
52回目 賭け金は4.000000 数列:1/1/1/2/3/ lose 勝敗差-6 手持ちのお金は996.000000
53回目 賭け金は5.000000 数列:1/1/1/2/3/4/ lose 勝敗差-7 手持ちのお金は991.000000
54回目 賭け金は6.000000 数列:1/1/1/2/3/4/5/ lose 勝敗差-8 手持ちのお金は985.000000
55回目 賭け金は7.000000 数列:1/1/1/2/3/4/5/6/ lose 勝敗差-9 手持ちのお金は978.000000
56回目 賭け金は8.000000 数列:1/1/1/2/3/4/5/6/7/ win 勝敗差-8 手持ちのお金は986.000000
57回目 賭け金は7.000000 数列:1/1/2/3/4/5/6/ lose 勝敗差-9 手持ちのお金は979.000000
58回目 賭け金は8.000000 数列:1/1/2/3/4/5/6/7/ lose 勝敗差-10 手持ちのお金は971.000000
59回目 賭け金は9.000000 数列:1/1/2/3/4/5/6/7/8/ lose 勝敗差-11 手持ちのお金は962.000000
60回目 賭け金は10.000000 数列:1/1/2/3/4/5/6/7/8/9/ win 勝敗差-10 手持ちのお金は972.000000
61回目 賭け金は9.000000 数列:1/2/3/4/5/6/7/8/ win 勝敗差-9 手持ちのお金は981.000000
62回目 賭け金は9.000000 数列:2/3/4/5/6/7/ lose 勝敗差-10 手持ちのお金は972.000000
63回目 賭け金は11.000000 数列:2/3/4/5/6/7/9/ lose 勝敗差-11 手持ちのお金は961.000000
64回目 賭け金は13.000000 数列:2/3/4/5/6/7/9/11/ win 勝敗差-10 手持ちのお金は974.000000
65回目 賭け金は12.000000 数列:3/4/5/6/7/9/ win 勝敗差-9 手持ちのお金は986.000000
66回目 賭け金は11.000000 数列:4/5/6/7/ lose 勝敗差-10 手持ちのお金は975.000000
67回目 賭け金は15.000000 数列:4/5/6/7/11/ win 勝敗差-9 手持ちのお金は990.000000
68回目 賭け金は12.000000 数列:5/6/7/ win 勝敗差-8 手持ちのお金は1002.000000 数字分解
69回目 賭け金は6.000000 数列:3/3/ win 勝敗差-7 手持ちのお金は1008.000000 数列リセット
(省略)
16653回目 賭け金は1305.000000 数列:284/453/737/1021/ lose 勝敗差-269 手持ちのお金は187.000000
16653回目で手持ちのお金が足りません
45回目から69回目までの一連の動きが、ルールを理解するのに、わかりやすいと思われるので、切り取っている。45回目終了して、46回目最初の段階で持っている金額は1007。68回目で勝利して、残った数字は6になる。6を分解して(3/3/)にする。69回目で勝利して、無事数列が全部消え、69回目が終わった段階で持っている金額は1008。最初の段階より+1される
※賭け金額の最小単位が2倍に増えた場合、数列の数字を2倍にするか、数列は2倍にせず、最後の賭け金額の計算の時に2倍にする、二つの流儀が考えられるが、ここでは、賭け金額の最小単位(m_min)が2倍になっても、数列は2倍にせず、最後の計算で2倍にする流儀に従う。
※数列の数字を2倍にする流儀だと、数字を分解するところの書き方が少しめんどくさくなるため。例えば、最後に残った数字が7の場合で、賭け金額の最小単位が2に増えた場合を考える。数列の数字を2倍にする(賭け金額を記録する流儀)にすると、14となり、普通に分解すると、(7,7)になる。賭ける金額が奇数になり、賭け金額の最小単位が2であるのと矛盾が起きる。この部分をきちんと処理できるように書くのは、少し骨が折れそう。
※サイズが1になった時の処理の、m_bet=vec[0];は、正確には賭けの金額ではなく、数列を考えるために、×m_minを抜いて考えている。また、負けた時のvec.push_back(m_bet/m_min); のm_bet/m_minの処理も同じく、数列で議論するため。
※size==0でなく、size<=0としているのは、払い戻し金額が3倍の時に、中に格納していた要素を削りすぎて、負の値をとる場合があるため。
(3倍の払い戻しの場合で、所持金が増えないケース)
14213回目 賭け金は1.000000 数列:0/1/ win 勝敗差-4721 手持ちのお金は8128.000000 数列リセット
14214回目 賭け金は1.000000 数列:0/1/ lose 勝敗差-4722 手持ちのお金は8127.000000
14215回目 賭け金は1.000000 数列:0/1/1/ lose 勝敗差-4723 手持ちのお金は8126.000000
14216回目 賭け金は1.000000 数列:0/1/1/1/ win 勝敗差-4722 手持ちのお金は8128.000000 数列リセット
14213回目終了後、14214回目開始の時の所持金は、8128。一方で14216回目最後の所持金も8128で増えていない。このように払い戻し3倍の時は、「数列がリセットされた時に、最初に持っていた金額よりも+1になる」が成り立たない。
※srand((unsigned)time(NULL));の部分で時間を参照した上で乱数を発生させている。そのため、実行するタイミングで結果が変化する。
モンテカルロ法(分解法)の期待値シミュレーション
上のプログラムを何度も走らせて期待値や破綻する率をもとめたい
※この期待値を求める時にモンテカルロシミュレーションの考え方が使用されている。
そのためのプログラムは以下のようになる
払い戻し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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
#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=0;n<=1;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min;//賭け金 int m_bet0;//分解用 int m_bet1; 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){ m_bet=vec[0];//数列の記憶 m_bet0=m_bet/2; m_bet1=m_bet-m_bet0; vec.clear(); vec.push_back(m_bet0); vec.push_back(m_bet1); size = (int)vec.size();//sizeの大きさ再度確認 } if(size<=00){ vec.clear(); for(int n=0;n<=1;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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
#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=0;n<=1;n++) { vec.push_back(n); } int size; size = (int)vec.size(); m_bet=(vec[0]+vec[size-1])*m_min;//賭け金 int m_bet0;//分解用 int m_bet1; 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){ m_bet=vec[0];//数列の記憶 m_bet0=m_bet/2; m_bet1=m_bet-m_bet0; vec.clear(); vec.push_back(m_bet0); vec.push_back(m_bet1); size = (int)vec.size();//sizeの大きさ再度確認 } if(size<=0){ vec.clear(); for(int n=0;n<=1;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
破たん回数は96838
破たん率は96.838000%
破たんするまでに行える勝負回数の期待値(i_exp)は12665.090120
破たんする直前で持っているお金の期待値(m_exp)は989.985760
破たんした時の勝負差の期待値は-0.449580
(3倍の払い戻し)
勝負回数は100000
試行回数は100000
破たん回数は99035
破たん率は99.035000%
破たんするまでに行える勝負回数の期待値(i_exp)は6358.175200
破たんする直前で持っているお金の期待値(m_exp)は1023.800680
破たんした時の勝負差の期待値は-2119.157820
2倍の払い戻しの破綻率96.838%は、高いように見えるが、マーチンゲール法やなどの99%越えと比べるとだいぶましな数字である。
もとになった単純なモンテカルロ法も破たん率は99%は超えていたので、悪くはない結果だろう。
破綻するまでに行える勝負回数の期待値(i_exp)も12665回と1万回を超える。
3倍の払い戻しの方の破綻率が高いのは、おそらく、2倍の払い戻しと比べて、3倍の払い戻しの場合、数列がリセットされた時に+1以上の利益を出すことがあるため、その分リスクも高まっているからだろう。
※3倍の払い戻しにおいて、かならず、+1以上の利益を出すわけではない。たまに、0の場合もある(個別シミュレーションの項目で説明済み)。数列がリセットされた時に、最初と比べて、負になることはおそらくない。
まとめ
上で提案した方法は、
- 主に、払い戻し2倍で使用されることを想定している。
- 払い戻し2倍において、数列がリセットされると、最終的な収支は+1される
- 単純なモンテカルロ法より、長くゲームが楽しめる
※もしも命名するなら「(0,1)分解法」とか「モンテカルロ分解法」、あるいは単純に「分解法」といったところかな……。私程度の人間が考えつくベットシステムなので、既にどこかで確立されている手法として存在しているかも……
書籍の紹介
『ギャンブルの必勝法が本当に儲かるかプログラミングで検証してみた』は以下の疑問にお答えします。
●勝率が50%の場合、利益を生み出す必勝法は存在するか?
●勝率が60%の場合、どのように賭けるのが最適か?
必勝法と思われている手法を15種類紹介します。必勝法には、例えば、マーチンゲール法(負けた時に2倍賭ける手法)などがあります。これらの手法が本当に儲かるかプログラミングを使用して検証します。また、検証するために必要なプログラミングの知識(C#)も紹介しています。
ギャンブルの必勝法が本当に儲かるかプログラミングで検証してみた
関連記事
※目次用の記事:ギャンブルの賭け方の種類をまとめてみた
~Webサイトを自分で作ってみませんか?~
Webサイトを運営するにはサーバーが必須です。
このサイトは、エックスサーバー のサーバーを使用しています。
エックスサーバーは無料で10日間お試しができます。
こんばんは。
凄く勉強になり、助かりました。
有り難う御座います。
負けた時に掛け金を増やしていく方法は、いずれ破綻するのですね。
それなら、1000から始めて1100や2000まで増やせる確率は
どれぐらいかわからないでしょうか。
いずれ破綻するのであれば、投資のように一定額で
利益確定してはどうかと思いまして。
もしわかるようであれば、宜しくお願いします。
有益なコメントありがとうございます。
マーチンゲール法で確率計算した記事をアップしましたので、もしよろしければ覗いてみてください。
マーチンゲール法で2倍儲かる確率を検証(c++/c言語)
こんばんは。
オンラインカジノのルーレットにハマっている者です。
MSaccessとスクリプトが少しできるので、いろいろなベッティングシステムを
試してみましたが有名どころのものはどれも大数の法則に追い付かず
散々な結果でした。
負け分を分散して回収する方法も自分なりに行ってみましたが
$0.1儲けるのに数時間と膨大な資金が必要と感じました。
ネットを模索しているうちにたどり着いた貴殿のレポートを
とても納得できる検証の仕方だと感じております。
さて、以下もネットで見つけたのですが検証していただけませんでしょうか。
「両賭けダランベール」
赤黒など両側からダランベール法を行うと両方合算でプラスになる。
というものです。
わたくしがExcelで拙いシュミレーションをしたところ早い段階でわずかにプラスになる
ことがあるように感じました。
ただ、「こんな簡単なことでプラスになるなら、ブラックジャックの複雑なカウンティング技術なんか必要ない。」と感じてそれ以上深追いしなかったものですが
よろしくお願いいたします。
コメントありがとうございます。
プログラムによるシミュレーションはしていませんが、儲からないと思います。
なぜなら、そもそも、ダランベール法自体、勝率50%のゲームで利益をだしません。
両賭けダランベールは、ダランベール法を実質2回分やっていることに相当するので、儲からないと思います。
サイトを見ていただいているので、ご存じかもしれませんが、完全に独立したゲーム(前の結果が次の結果に影響を与えないゲーム)で、勝率50%の場合、儲けることはできません(私の知る限り)。
いくら前回の勝ち負けの結果をもとに、資金の配分をいじくっても意味がないことを悟りました。
そのため、私は、一部を除き勝率が50%を超えないギャンブルに対して、調査を行っていません。FXや株は頑張れば、勝率50%を超えると思っているので、現在研究中です。