バックテスト編バックテストと実際の成績乖離の原因2~過去足の情報の不足~ある同じ期間についての1時間足でバックテスト結果と実際のトレード成績が綺麗に一致しない場合があります。この1つの理由として、バックテストでは時間中の値動きの順序を把握できないという問題があります。ポジションの積み増しやトレイリングストップなど、損切りの位置を値動きに応じて動かすロジックを有効にしているとき、この問題が生じます。バックテストの1時間足では、高値と安値のみが記録されており、その順番までは記録されていません。そのため、ポジションの積み増しやトレイリングストップを有効にしている場合、この順番によって「バックテストでは損切りにかかっているのに実際はポジションを保持している状況」が生じたり、またはその逆の状況が発生することがあります。875000円この1時間足の情報からは以下の2つのストーリーが考えられますが、バックテストではそのどちらが起きたのかを判定できません。実際のログ以下は実際のログです。バックテストの結果と一致しなかったので調べてみたところ、バックテストでは損切りにかかっていました。実際には安値をつけたあとに高値をつけたので、ポジションの清算は発生しませんでした。527176トレイリングストップの発動:ストップ位置を472880円に動かして、加速係数を0.2に更新します472880円の損切ラインに引っかかりました。539587他にも同様の現象として、高値の更新によるポジションの積み増し(と、それに伴う損切ラインの引き上げ)があります。この場合も、バックテストで直近の安値が更新後の損切ラインにかかっていた場合に、実際には損切りにかかったのかどうかを判断できません。解決策この問題は1時間足の情報量が足りないことに原因があるので、バックテストプログラムの改良では解決できません。根本的にこの問題を回避するには、1時間足のバックテストに1分足を使うか、あるいは実際の売買でリアルタイムの価格を一切使わないロジックに変更するしかありません。妥協策として「高値をつけたあとに安値をつけてまた高値で終わる可能性はやや低い」と考えることはできます。「始値を2回またぐ可能性は低い」と考えるわけです。安値の順番に動いたと仮定します。この場合は単に以下の順番でロジックを適用します。終値」の順番で動いたと仮定します。1.買いポジションを保有している場合1)損切りの関数を適用2)手仕舞い(逆シグナル)の関数を適用3)ポジションの積み増しの関数を適用4)トレイリングストップの関数を適用2.売りポジションを保有している場合1)ポジションの積み増しの関数を適用2)トレイリングストップの関数を適用3)損切りの関数を適用4)手仕舞い(逆シグナル)の関数を適用もちろん「始値>終値」の場合は、それぞれが逆になります。)もちろんこの方法も完璧ではありません。始値と終値がほとんど同じ水準の場合などは、始値の水準を何度もまたぐことは珍しくありません。揉みあいの場面では、バックテストの1時間足でどちらの順番に動いたかを判断することは不可能です。大きな値動きを伴う場面では、この順番で判定した方が(バックテスト上で)致命的な順番間違いを犯す可能性を減らすことができると思います。コメントを残すモンテカルロ法で固定額トレードと定率(複利)トレードの運用成績を比較しよう前回の記事では、実際に得られたトレード結果からランダムにリターンを復元抽出し、疑似的なトレード結果を生成することで最終資金(運用成績)のバラつきをシミュレーションするブートストラップ法について解説しました。今回はその続きで、定額トレードで運用した場合と定率トレードで運用した場合の最終資金をモンテカルロシミュレーションで比較し、どのくらい結果のバラつきに違いがあるかを調べてみようと思います。~2019/5/19(執筆時)の1年間のデータを使って検証します。手法は1時間足のチャネルブレイクアウトでトレンドフィルターのみ有効にしたものを利用します。50万円レバレッジ3倍まで1.固定額トレード固定額トレードでは、1トレードあたり100万円分のBTCを売買します。資金量の変動に関わらずレバレッジをかけて一定額(100万円)でトレードし続けます。原資が33万円を割って100万円の売買ができなくなったら終了です。最大レバレッジは3倍ですが、資金が増えるにつれてレバレッジをかける必要がなくなるので、後半で資金効率が落ちてます。最終的な資金効率としては、1回のトレードで平均して1.18倍のレバレッジをかけ、証拠金に対して0.97%の利回りを得ていたことがわかります。利益合計は110万2422円、運用パフォーマンスは320%です。2.定率トレード定率トレードでは、常に口座残高の2%のリスクを取ってトレードします。この具体的な意味は、口座残高の2%を損切り幅で割って取得するポジションサイズ(枚数)を決定する、という意味です。詳しくはこちらの記事で解説しています。資金が増えれば増えるほど取得できるポジションサイズが大きくなるため、いわゆる複利運用にあたります。当然ですが、固定額トレードと定率トレードは全く同じ場面でエントリーして損切りや手仕舞いをしています。そのため、トレード回数や勝率、賭け金に対する平均リターンなどの数字は全て同じです。定率トレードは複利運用をしているので、同じリターンでも証拠金に対する利回り(資金効率)が違います。上の図を見てわかるように、定率トレードは資金が増えたあとも資金効率(レバレッジの比率)が変わっておらず、平均して1回のトレードで1.879倍のレバレッジをかけています。この差が最終成績の違いとして現れています。最終利益は183万2789円、運用成績は467%です。この違いは、以下の「10%を超えるリターン」の利回りを比較するとわかりやすいです。BUY------------------------------------------どちらも同じ場面で(賭け金に対しては)同じリターンを得ていますが、証拠金に対する利回り(資金効率)がかなり違うのがわかると思います。疑問に思うこと良かった結果だけを見れば「複利で運用した方がいい」という結論になるのは明白です。最初から絶対に勝てるとわかっているなら、複利で運用しない理由はありません。最終的な運用成績は、ある時点からある時点を切り取ったものにすぎないので、切り取る場所を変えれば結果もかわります。どの時点の成果をもって「結果が良かった」と判断するかは難しい問題です。実際にレンジ相場に突入したときにトレンドフォローBOTを止めたくなる理由もここにあります。定率トレードの一番の問題は、今回のスイングトレンドフォローBOTのように、勝率が低くかつ売買頻度もそれほど多くない売買ロジックを使用した場合、複利で運用すると「大半の人は平均(期待値)通りのパフォーマンスを上げられず、最頻値や中央値はそれよりも低い方向に偏る」という点です。これは最終資金が対数正規分布に近似するからで、詳しくは前回の記事(最終資金のモンテカルロシミュレーション)でも説明しました。定率トレード全般にいえることですが、BOTの勝率が低い場合により顕著にその影響が出ます。簡単にいえば、一般的には複利の方がドローダウンのリスクが大きいということです。今回の結果でも最大ドローダウンは定率トレードの方が大きくなっています。そこで疑問なのは、ある場面で定額トレードと定率トレードのどちらの方が最善なのかを、トレード結果を元に何らかの指標で定量的に判断できるのだろうか?ということです。モンテカルロシミュレーションで試すことブートストラップ法(復元抽出)で最終資金をシミュレーションした場合に、定率トレードでどのくらい最終資金にバラつきが見られるかは、前回の記事で確認しました。今回は、同じく定額トレードのモンテカルロシミュレーションで最終資金の分布を確認して、その違いを比較してみたいと思います。1.扱うサンプルの違い前回の記事でも簡単に説明しましたが、定率トレードと定額トレードでは、復元抽出に使うリターンの種類が違います。定率トレードでは、全てのリターンを初期資金に掛け合わせた結果が最終資金と一致しなければなりません。そのため、リターンの数字には「証拠金に対する利回り」を使います。定額トレードでは、全てのリターンを固定額(100万円)に掛けて利益額を計算し、それを初期資金に足し合わせてシミュレーションします。そのため、リターンの数字には単に「賭け金に対するリターン」を使います。(148個)-1.155,11.162,-1.03,-3.291,-2.653,-1.264,0.001,-1.231,-1.464,0.549,-1.595,12.182,-2.405,-1.583,-1.817,4.976,-1.71,-1.667,-1.089,-1.329,-1.037,-0.797,0.945,-0.863,-1.53,13.967,-1.068,-1.282,4.747,-1.979,11.542,6.341,-2.693,-1.643,-2.667,-2.257,-1.619,-1.456,-1.636,-1.611,6.54,3.406,9.156,-1.78,-1.478,-0.153,-1.343,2.637,-1.436,0.765,-1.278,-0.991,-1.306,0.647,-0.429,-0.63,-0.502,5.061,-1.101,-2.237,-0.713,-0.551,-0.378,-0.505,-0.459,-0.592,-0.534,-0.613,-0.461,-0.397,-0.692,-0.392,0.85,-0.604,-0.597,-0.341,12.832,30.902,-3.179,-5.269,-3.406,10.836,-1.878,3.361,11.548,-2.498,-3.438,-1.545,-1.177,-2.967,-1.19,-1.538,-0.973,-1.24,4.61,-1.148,-0.994,-0.697,-0.756,-1.682,-1.101,-0.9,2.797,-1.185,-0.839,-0.654,-0.458,-0.652,-0.547,-0.401,6.179,-0.974,0.275,-0.913,9.316,-1.377,0.067,-0.716,-0.919,0.29,-0.951,-2.695,-0.994,-1.24,1.29,-0.821,-0.204,-0.904,25.021,0.799,-0.184,-0.907,0.45,-1.007,-1.016,-2.842,-1.231,-1.227,-0.196,2.04,-1.001,-1.917,-1.022,4.116,-1.839,-1.583,36.507,-4.9812.定率トレードの最終資金のシミュレーション定率トレードの場合の最終資金のモンテカルロシミュレーション(ブートストラップ法)については、すでに前回の記事で解説しているので、先に結果を見てしまいましょう。定額トレードと比較の条件を揃えるために少しパラメータを変えたので、前回と結果は少し違っています。466.5%)ここで現実離れした右側の数値に意味があるわけではありません。正のリターンはサンプル数が少なくバラつきも大きすぎるからです。そのため、平均値が過大になっていますが、759%という数字が実際の市場で期待できる平均値という意味ではありません。ここでは1トレードあたりの勝率が低いことが原因で、最頻値が中央値よりかなり下方に偏っているという情報が重要です。3.定額トレードの最終資金のシミュレーション次に定額トレードの最終資金を、同じくブートストラップ法(サンプルの復元抽出)でシミュレーションします。先ほども説明したように、定額トレードの場合は「賭け金に対するリターン」の数字からランダムにリサンプリングし、それを固定額(例:100万円)に掛け合わせた結果を初期資金に順番に足し合わせるプログラムを書きます。3つがほとんど一致しています。いずれも150万円~160万円で、運に結果が左右される可能性が定率トレードに比べて低いことを意味しています。多数の人が、期待値とおりの成績を上げられる可能性が高いということです。1602422円(320.0%)ただし5%以下の運の悪さに遭遇したときの成績はほとんど同じです。0円まわり(破産確率)は定額トレードの方が高いくらいです。これは当たり前ですが、口座残高のn%を賭ける定率トレードは、数字上は破産しないからですね。定額トレードは、負けても賭け額を減らしたりしないので、正の期待値がなければ破産します。判断するかを考える定率トレードの最終資金の確率分布は対数正規分布に近づき、一方、定額トレードの最終資金の確率分布は正規分布に近づくことが理解できました。このグラフを眺めるだけだと、単に「定率トレードはリスクも大きいけどリターンも大きいよね」という、よく本に書かれている結論しか出てこず、結局どう意思決定すればいいのかわかりません。今回のケースの場合、中央値は定率トレードの方が高いものの、最頻値は定額トレードの方が高いというヒントになりそうな有益な情報が得られました。いうことは、最初の疑問に答えるためには、「最頻値」と「中央値」のどちらを信じてトレードするべきか?という問題が鍵になりそうです。私も詳しくありませんが、統計学の世界ではこのように「確率分布の中からどれか1つだけ代表値を選ばなければならない場面での意思決定の方法」についての学問分野があり、それを統計的意思決定というそうです。結局のところ、「目的に応じて最適な数字を選ぶしかない」というありきたりな結論になるのですが、せめてそれを関数にして最適解を求めようということです。このような関数を損失関数というそうです。ファイナンスの用語で損失関数という言葉を聞きますが、そういう意味なんですね。(参考:「pythonで体験するベイズ推論」5章より)例えば、以下のような指標を作って定量的に判断します。(漠然とした気持ち)「定率トレードの方が大きく儲かる可能性が高いなら、多少のリスクを背負っても定率トレードにしたいなぁ。固定額トレードの平均を下回るのは悔しいよな...。その可能性の方が高いならやめておこうかなぁ...。」(統計的意思決定)原則として定率トレードを選択する。定率トレードの最終資金が定額トレードの期待値を下回る確率が50%を超えるなら定率トレードはやめる。2.損失関数をつくろうモンテカルロシミュレーションの素晴らしいところは、数十万回のトレード結果の事後分布を得られるので、上記の確率を実際に計算できることです。「最終資金が定額トレードの平均を下回る確率が50%を超える」かどうかを判定する損失関数を作りましょう。0.5:print("定額トレードを選択すべき")else:print("定率トレードを選択すべき")これだけでOKです。モンテカルロシミュレーションで得られた10万回分の最終資金の結果がリスト形式で保存されています。二値の配列に変換します。真偽値のTrueは1、Falseは0なので、そのリストの平均を取れば、Trueの確率(全ての最終資金のうち160万円を下回った結果の割合)を計算できます。二値変数に変換して、Trueの割合から確率を推測する方法は、ベイズ推計(事後確率)の基本的な考え方のようです。さきほど紹介した書籍のほか、本「基礎からのベイズ統計学」6章などでも紹介されています。定率トレードの結果が、定額トレードの平均的な結果を下回る確率は約36%であり、最初に立てた意思決定の基準に照らし合わせると定率トレードを選択することが合理的である、とわかります。注意点このようにモンテカルロシミュレーションの優れた点は、実際に意思決定するときに必要となる基準点の確率をちゃんと計算できる点にあります。以下のグラフでいうと、最終資金が160万円を下回る確率が35%だとわかる、ということです。このような意思決定を行う場合には、結果を見る前に意思決定の基準を決めて、それを守ることが大事です。売買ロジックの評価と全く同様で、損失関数のパラメータ(ここでは160万円と50%という数字)も自由度の1つなので、ずるずると弄るといくらでも望むような結果を出せてしまいます。最初から「本当は定率トレードでやりたい..」という思い込みがあり、上記の結果を見たあとに、もし悪かったら「じゃあ100万円を下回る確率ならどうだろう..」「じゃあ40%以上ならどうだろう?」と結果を見ながら数字を変更しはじめると、損失関数を使うことに何の意味もないということです。2件のコメントブートストラップ法(復元抽出)でBOTの運用成績をシミュレーションするある勝率と期待値をもつBOTが、実際にある一定期間でどのくらいの運用成績を得られるか?を判断するのは非常に難しい問題です。定額ではなく定率(複利)でトレードをする場合、なおさら予測は困難になります。ある売買ロジックでBOTを運用した結果、1年間で以下のような結果が得られたとします。実成績でもバックテストの結果でも構いません。-273098円これは実際にあるパラメータで1時間足のチャネルブレイクアウトBOTを(口座のリスク率2%)で動かした場合の運用成績の例です。疑問この結果だけを見ると最終成績は良好に見えます。1年間で3倍以上になっていますから。問題なのは、『この最終成績390%という数字はどの程度、運が良かった結果なのか?』という情報が、最終成績の数字を見てもよくわからない点にあります。この数字だけではわかりません。今回の記事では、判断の目安として、得られたリターンのサンプルから何度も復元抽出(リサンプリング)をして、ありえた最終成績のシミュレーションをするブートストラップ法を紹介します。ブートストラップ法(復元抽出)ブートストラップ法とは、1度だけしか抽出できないサンプルを再利用して大量のトレード結果を疑似的に生成するモンテカルロシミュレーションです。数学が得意でないので、厳密な定義はわかりませんが、多分そんな感じです。この方法の優れたところは、リターンがどんな確率分布に従うのかわからない(正規分布などでモデル化できない)場合でも、実際のサンプルから好きなだけ疑似的なトレード結果を生成できることです。復元抽出(=1度選んだ玉をバケツに戻す方式)なので、実際にありえる値の中から実際にありえる頻度(確率)に近い形でリターンを選んで、それを使って異なる結果をシミュレーションすることができます。同じサンプルを何度も使ってるだけなので、何万回も試行を繰り返せば同じ分布になります。1年間の成績(161回のトレード)という少ない試行回数の制限の中では、どの程度、結果がバラつく可能性があったかをシミュレーションできます。pythonコードブートストラップ法のイメージを掴むために、疑似的なトレード結果のサンプルを合計25セットほど作成してその結果をグラフにしてみましょう。csvファイルに集計-2.135,15.292,-1.133,-1.02,-1.843,-2.156,-0.794,-1.107,-1.083,-0.031,-1.068,28.578,-1.034,-1.547,-0.599,7.268,-1.419,-0.417,-2.124,-1.076,-1.109,-1.172,1.587,-1.981,-1.087,41.402,-1.122,-1.09,2.864,-1.089,7.148,3.553,-2.005,-1.972,-2.023,-1.865,-1.085,-1.092,-1.096,-1.079,0.169,7.34,4.487,6.202,-1.068,-1.079,-0.852,-1.234,1.611,-2.103,0.805,-1.13,-1.084,0.782,-1.286,-1.215,-1.285,14.745,-1.112,-1.074,-1.17,-1.251,-1.133,-1.258,-1.3,-1.219,-1.54,-1.213,-1.296,-0.761,-1.169,-1.176,-1.507,-0.509,-0.538,-1.212,-0.798,22.072,40.899,-1.802,-1.051,-2.005,-1.056,-1.574,-1.059,2.483,-1.657,3.692,14.52,-1.598,-1.039,-2.046,-1.062,-1.394,-1.955,-1.13,-1.038,-1.106,-1.077,-1.216,-2.013,13.119,-1.114,-1.134,-1.178,-0.287,-1.059,-1.112,-1.151,-1.501,0.696,-2.151,-1.174,-1.236,-1.319,-1.206,-1.496,-1.202,12.396,-1.14,0.541,-1.133,22.143,-1.593,-2.306,-1.196,-1.131,0.01,-1.16,-2.036,-2.2,-1.103,1.774,-0.634,-1.143,-0.973,-1.93,13.559,0.187,-1.796,-1.816,-2.214,-0.033,-2.169,-2.207,-1.051,-1.095,-2.146,-0.44,4.444,-1.131,-1.112,-1.134,5.13,-1.066,-1.092,35.116,-2.012,-1.027,-2.018,-0.996まずは上記のように、実際のトレードまたはバックテストで得られたリターンの結果(161個)を羅列して1つのcsvに保存します。次に以下のようなコードをpythonファイルで保存して、読み込みます。label="95パーセンタイル区間")plt.grid()plt.xlabel("復元抽出した平均リターンの分布")plt.ylabel("頻度")plt.legend()plt.show()上記のコードは、ブートストラップ法によって生成した疑似トレード161回の結果を合計25セット生成し、その勝率、平均リターン、ヒストグラムを図にするコードです。1セットあたりのトレード回数(=実際のトレードやバックテストで集計されたトレード回数)です。それを1セットとします。ブートストラップ法で何セット疑似サンプルを生成するかを指定する数字です。ここでは5×5で綺麗に図にしたかったので25セットにしましたが、何セットでも構いません。欲しいだけいくらでもデータを生成できます。実際、あとで10万セット生成して運用パフォーマンスをシミュレーションする例を紹介します。実行結果このように25セットの疑似データを生成することができました。同じサンプル(実際のトレード結果)から再抽出してるので、どれもパッと見では似たようなリターン分布になっています。左上の4個を拡大したものが以下です。0.48%~1.94%まで大きなバラつきがあります。疑似生成した25セットのトレード結果の平均リターンをヒストグラムにしたものです。赤い線は、161回のトレードの平均リターンを25回採取したものの平均値(=つまり平均リターンの平均)です。このように25回ほどシミュレーションを繰り返してみた結果、大体どの場合でも平均リターンは一応プラスになりそうだということ、年間161回という少ない試行の中では、運が悪ければ勝率が13%程度ということもありえた、ということ、などが想像できます。資産カーブをグラフ化するそれでは次に、いよいよ知りたかった「最終的な運用パフォーマンスにどのくらいのバラつきがありえたのか?」を検証してみましょう。ブートストラップ法で疑似的に生成したトレード結果を最初の資金に適用して、最終資金のシミュレーションをします。注意点として、ここで用いる「リターン」のサンプルが、証拠金利回りの数字になっていることを確認してください。この数字を用いないと、初期資金とリターンを掛け合わせた結果が最終的な資金と一致しません。単なるリターンと証拠金利回りの違いはこちらの記事で解説しています。)plt.legend()plt.show()これを実行すると以下のようになります。「太い黒線」が実際のトレード(バックテスト)で得られた資産カーブです。25回のシミュレーション結果を見るだけでも、最終資金は、50万円~300万円以上まで幅広くバラついているのがわかります。運が悪ければ、ほとんど資産が増えなかったケースもあり得たかもしれません。ちなみに1000回ほどシミュレーションすると以下のようになります。10万回シミュレーションする上記のグラフを見ても、「どのくらいの最終資金になる可能性が高いのか?」「最終資金が50万円(初期資金)を下回る可能性はどのくらいあるのか?」いまいちわかりません。ブートストラップ法で10万回ほど疑似トレード結果をシミュレーションして最終資金のヒストグラムを作りましょう。376.1%)実際の成績は194万円(390)%でしたが、これだけ勝率が低くかつ利益のバラつきが大きい売買ロジックでは、最終資金が120万円(最頻値)~190万円(中央値)くらいに終わってもおかしくなかったことがわかります。運が悪くても初期資金50万円を割る可能性は5%以下とかなり低いようです。ありえた「平均リターン」の分布トレンドフォロー戦略のBOTの場合、そもそも1トレードあたりの勝率が低く、かつ利益が出た場合のバラつきが極端に大きいので、「実際の成績よりも利益が増えた場合」のシミュレーション結果には、ほとんど意味がありません。中には最終資金が1000万円を超えるようなシミュレーションもありますが、それは単に勝ちトレードのサンプル数が少ないことが原因です。復元抽出なので、10万回もテストすれば極端に高い利益を何度も選びなおす可能性もあります。当然ですが、実際に市場でそのような利益機会が生じた可能性を意味するわけではありません。「実際の成績よりも悪かった場合」のシミュレーションはそれなりに信憑性があると思います。市場の過去データは1通りしかないので、何度サンプルを取り直したところで本当の意味で複数のシミュレーションをすることはできません。負けトレードの結果はサンプル数も多く分散も小さいので、現実にありえない値を取る可能性は低く、本当の勝率が想定より低かった場合をうまくシミュレーションできます。同時に、極端に1回だけ高い利益が出たことで全体の成績が実際より良く見えている場合(外れ値の影響)は、ブートストラップ法で何度もリターンを復元抽出することで、その影響を弱めることができます。補足:数学的にわかること上記のような最終資金のモンテカルロシミュレーションの意味を、もう少し一般的に理解するために、少し数学的に考えてみましょう。トレードの成績をもっとシンプルに、(1)勝率、(2)勝ったときの平均利益、(3)負けたときの平均損失、の3つのパラメーターだけで決まる確率変数と仮定します。すると最終資金は以下のように決まります。最終資金は、勝ちトレードの回数によって決まる関数です。この「勝ちトレードの回数」は、勝率pによって決まる確率変数なので、数学的には以下のような二項分布でモデル化できます。二項分布という確率分布は、試行回数(N)が十分に大きいとき(勝率が極端に小さくなければ)、正規分布に近似できることが知られています。(参考wikipedia)ということは、最初の式に戻ってみると、「勝ち回数」を表す指数の部分(x乗)は正規分布で近似できることがわかります。指数のところが正規分布に従うような確率変数を、対数正規分布といいます。対数正規分布というのは、トレードをしている方の多くが聞いたことがあると思いますが、0を閾値として右側にファットテールになる、以下のような形の確率分布です。stats.lognorm.pdf(x,s))plt.show()さきほどの最終資金のヒストグラムに似ていますね。このような確率分布の特徴として、最頻値や中央値が平均値よりも小さくなる(左側に偏る)という性質があります。いわゆる「数少ない一部の人が大勝ちし大半の人は平均より低い成績に偏る」という分布で、年収の分布などと同じです。持ってるかに関わらず、基本的には最終資金のグラフの形はこのような感じになる、ということです。グラフの形状自体に意味はありません。どのような売買ルールを採用しても多分こうなります。有益な情報ではこのブートストラップ法による最終資金のモンテカルロシミュレーションから、どのような情報を得ればいいのでしょうか。1つは「最終資金のバラつきがどのくらい大きいか?」です。同じモンテカルロシミュレーションで、勝率50%、期待利益2%、期待損失1%のBOTの運用をシミュレーションしてみましょう。すると最終資金は以下のようになります。トレード回数161回たしかに僅かに右側に偏った分布になっており、最頻値<中央値<平均値のかたちになっていますが、その差はほとんどありません。このような分布であれば、最終的な資金(運用成績)についてある程度の精度で確信を持つことができそうです。勝率10%、期待利益14%、期待損失1%のBOTを考えてみます。こちらのBOTも期待リターンは同じ(+0.5%)ですが、最終資金のヒストグラムはもっと歪んだ形になることが予想できます。先程のBOTと1トレード当たりの平均リターンは全く同じにもかかわらず、最終資金の分布は大きくバラつき、ハッキリと勝ち組と負け組に分かれてしまっています。中央値はあまり変わりませんが、最頻値はさきほどより遥かに低くなっています。一方で期待値はかなり大きくなっています。さきほどの分布とは異なり、5パーセンタイル値が40万6381円で原資割れしています。これは運が悪ければ、初期資金を下回る可能性もあったことを示しています。最後のシミュレーションには以下のコードを作りました。コメントを残すBOTの一番成績のいい最適なパラメーターをpythonで総当たりで探索しよう!さて、前回までの記事でバックテストをして自動売買BOTのパフォーマンスを評価する方法は、大体マスターできたと思います!今回は、バックテスト編の総仕上げとして最適なパラメーターを探索する方法を紹介しておきましょう。このパラメーター探索ができることこそ、ある意味、自分でpythonのプログラミングができることの優位性といっても過言ではないかもしれません。それを調べる方法を解説します。パラメーター最適化とはパラメーターとは、ある自動売買BOTのロジックの中で自由に動かして変更できる値のことをいいます。前回までの記事で学習した一番シンプルなn期間ドンチャン・ブレイクアウトBOTには、以下の2つのパラメーターがありました。1)X分足の時間軸を使う2)n期間の最高値(最安値)ブレイクアウトで買う同じドンチャンブレイクアウトでも、15分足で10期間のドンチャン・ブレイクアウト戦略を使うのと、1時間足で30期間のドンチャン・ブレイクアウト戦略を使うのとでは、全く成績が異なります。上記の2つの組み合わせだけなら、前回までの記事の方法で1つ1つを手作業で検証することも可能です。組み合わせが何百通り、何千通りと増えてくると、すべてのパターンを手作業で検証して比較するのは難しくなります。そこでpythonの出番です。pythonのようなプログラムなら、自動的に何百通りものパラメーターの組み合わせでバックテストを行い、最も利益を最大化できるようなパラメーターを探索することができます。今回はその方法を解説していきます!今回の練習で試すパラメーターまずは準備として前回までのドンチャン・ブレイクアウトのコードを改良し、以下の4つのパラメーターを設定できるようにしてみましょう。使うより実践的なドンチャン・ブレイクアウト戦略では、上値のブレイクアウトと下値のブレイクアウトで異なるパラメーターを使用することがよくあります。そのため、今までのコードを改良し、買いエントリと売りエントリで、それぞれ違う期間のパラメーターを設定できるようにしておきます。今までのコードでは、ブレイクアウトの判定基準を「過去n期間の最高値(最安値)を直近の高値が上回ったとき」としていましたが、高値ではなく終値のパターンも検証しておきましょう。上記の修正済みのコードは、以下の別記事にまとめておきます。時間のある方は自分でも挑戦してみてください!パラメーターを設定できる改良版コードパラメーターの組み合わせを総当たりでテストする方法パラメーター探索と聞くと、最近流行りの機械学習やニューラルネットワークなど、難しい数学やプログラミングスキルが必要だと誤解している方もいるかもしれません。基本的には、数千通りくらいのパターンであれば、ただの「総当たり」で全パターンを計算すれば十分です。全パターンを試しても数十秒~1分もあれば終わりますので、複雑な機械学習や最適化アルゴリズムは必要ありません。以下のようなパターンの組み合わせをすべて試したいとしましょう。]この場合、以下のようにfor文を書くことで全パターンの組み合わせを試すことができます。各組合せで実行したい処理パラメーターAのfor文処理をするなかで、1つの要素についてさらにパラメーターBのfor文処理をし、パラメーターBのfor文処理のなかの1つの要素に対してさらにパラメーターCのfor文処理をし....という入れ子構造にしていくわけですね。500通りの組み合わせについて、全てのパターンでバックテストを実行することができます。シンプルな記述方法なお、pythonでは上記のコードをもっとシンプルにして以下のように書くことができます。試すパラメーターの組み合わせなお、今回のドンチャンブレイクアウトのパラメーター最適化では、以下の組み合わせを総当たりで全て試してみることにします。768通りのパターンをテストしてみましょう。これをfor文の総当たりでテストするためには、以下のように書けばOKです。(今までの記事で作成したもの)これで総当たりのfor文の準備は完了です!最大化したい指標を決める何のためにパラメーター最適化を実施するのかといえば、リターンを極限まで高くしたいからですよね。「総利益が最大になるようなパラメーターを探す」というのも1つの方法です。自動売買BOTの評価を最終損益だけで判断するのは不十分です。厳密にいえば、私たちが探している理想の売買ロジックは、「できるだけリスクを抑えながら、できるだけ大きなリターンを得ること」にあるはずです。そのため、リスクとリターンの比率を1つの数字で表せるような指標を「最大化したい数字」に設定すべきです。このような指標には、シャープレシオ、MARレシオなど、いくつかの指標がありますが、ここでは一番オーソドックスな「プロフィットファクター」を使うことにします。損失100万円」の売買システムとでは、かなり意味が違ってきます。前者は、損失が利益のおよそ1/10で済んでいるのに対し、後者は、なんと利益の半分もの損失を出してしまっています。前者の方が安定した売買システムで、後者のほうがより不安定な(リスクの高い)売買システムということになります。」で表される指標のことをプロフィットファクター(PF)といいます。PFが大きければ大きいほど、そのシステムは安定していてリスクが小さいと評価できます。プロフィットファクターの計算コード今まで作成してきたドンチャン・ブレイクアウトのバックテスト検証コードに、プロフィットファクターを計算するコードを追加しておきましょう。2)さて、それでは実際のコードを作っていきましょう!2.pythonコードいつものように、まず最初に全コードの内容と実行結果を示します。その後コードの書き方を解説していきます。)実行結果これを実行すると以下のようなCSVファイルが出力されます。プロフィットファクター(PF)のJ列を「降順」で並び変えてみると、ドンチャン・ブレイクアウトの有望なパラメーターの組み合わせがわかります。PFで並び替え後の成績ベスト30上記のコードではトレード回数が100回に満たなかった結果データを全て捨てているので、どのパラメーターの組み合わせも最低限のサンプル数を確保できています。上位30の成績のパラメーターをみると、ほとんどが2時間足以上の時間軸なのがわかります。PFで並び替え後の成績ワースト30逆に最もプロフィットファクターの低かった(悪かった成績)30コをみると、短い時間軸(30分足)に集中しているのがわかります。ドンチアンブレイクアウトは、あまり短い時間軸には適さない戦略の可能性がありそうです。(ただし時間軸によってテストに使用しているローソク足の期間が違うので、厳密には比較できない点に注意してください。)実行結果2次はさらに範囲を絞ってテストをしてみましょう!今度は時間軸を1時間足・2時間足だけに絞り、上値ブレイクアウトの期間・下値ブレイクアウトの期間を10~55期間の範囲にして1期間刻みでテストしています。以下のようにパラメーターを設定すればOKですね。np.arange(10,56)この条件でテストしてみると以下のようなCSVファイルが出力されます。・CSV結果出力ファイル1・CSV結果出力ファイル2ファイル1は先ほどと同じくトレード回数が100回に満たなかったテスト結果を削除したもの、ファイル2は削除せずに全ての結果を残したものです。今回はファイル2を使ってみましょう。PFで並び替え後の成績ベスト30(1時間足)1時間足の上位の成績を見ると、明らかに上値ブレイクアウト(買い)の期間は40前後、下値ブレイクアウト(売り)の期間は20前後が良さそう、という傾向が見えます。判定基準は「終値」が上位を独占していますね。PFで並び替え後の成績ベスト30(2時間足)2時間足の上位の成績を見ると、上値ブレイクアウト(買い)の期間は50以上、下値ブレイクアウト(売り)の期間は20~30期間がいいようですね。「終値」でブレイクアウトの判定をした方が、トレード回数は減るものの成績が良くなる傾向にありそうです。使って調べてみるといいでしょう。Pythonコードの解説さて、それでは新しく変更した部分のpythonコードを解説しておきましょう!基本的なコードは前回まで勉強した内容とほとんど変わっていないことに気付いたと思います。集計するコード」を、まるまる上記で説明した「総当たりのfor文」の中に入れるだけですね。図にすると以下のような感じです。こののような入れ子構造になっている点だけ混乱しなければ、上記のコードで難しい箇所はなかったと思います。集計・計算した結果を表示(print)するだけでしたが、今回のパラメーター最適化のコードでは、各バックテストの結果を返すように変更する必要があります。そのためバックテスト集計用の関数に以下の部分を足しています。result各バックテストの結果を配列に記録して、それを最後にpandasで1つの表にする方法は、前回までの記事の「各トレード結果を配列に記録して最後に集計する方法」と全く同じなので、説明は省きます。不要な行を削除する唯一新しく登場したのは、最後のdrop()の箇所でしょう。今回のコードでは、トレード回数の少ないパラメーターの組み合わせを除外できるようにしています。それが以下の部分のコードです。)表名.drop()は、行のindexを指定することで、その行を表から削除することのできる関数です。これを使って以下のような仕組みで、トレード回数の少なすぎるバックテスト結果を削除しています。)なお、inplce=Trueは、現在の表データに結果をそのまま上書きする、という意味です。以上でコードの解説はおしまいです!まとめこの章の前の方の記事でも解説したように、このようなパラメーター調整には常にカーブフィッティングの問題がつきまといます。以下のような点に注意しておきましょう。(1)パラメーターの数を増やし過ぎないようにする(2)おおまかな傾向を捉えることを目的にする(3)トレード数(サンプル)が少なくならないようにする一般論としていえば、今回のドンチャン・チャネル・ブレイクアウトのように広範なパラメーターのどの値を使ってもプラスの期待値が出る、という場合で、おおまかな傾向を捉える目的(例えば、20期間あたりが一番良さそう)でパラメーターを最適化するのは問題ありません。問題があるのは、例えば、「6」という数字を使えば利益が出る、「8」という数字を使うとマイナスに転落する、「13」という数字を使えばプラスになる、といった全く連続性も傾向もないパラメーターを恣意的に調整することです。これをすると、過去データでしか利益の出せないパラメーター調整になります。今回の記事で、いったんバックテスト編はおしまいです。2件のコメントドンチャン・チャネルブレイクアウトで買い/売りの期間を別々に設定できるようにする次回の記事「pythonで最適なパラメーターを自動的に探索する」での練習にあたって、まず先に以下のようなパラメーターを自由に設定できるようにドンチャン・ブレイクアウトのコードを改良しておきましょう。使う今回の記事では、これらのパラメーターを設定できるようにコードを修正する方法を解説し、最後に1時間足で買い(30期間)、売り(20期間)、判定基準(終値)でのバックテスト結果を見てみましょう。Pythonコード先に改良版のコード全体を記します。"件のローソク足データで検証")print("--------------------------")backtest(flag)これでドンチャン・ブレイクアウトの自動売買ロジックに、上値のブレイクアウトと下値のブレイクアウトとで異なる期間を設定できるようになりました。ブレイクアウトの判定基準について、高値・安値を使うことも、終値を使うこともできるようになりました。今までの「最もシンプルなドンチャン・ブレイクアウト」では、買い・売りともに同じ期間(例えば30期間)を使っていました。そのため、ブレイクアウトを判定する際に、比較に使うための過去のローソク足データは、配列に30個ぴったり用意しておけばOKでした。買い・売りで別々の期間を設定する場合は、そうはいきません。そこで、ドンチャンブレイクアウトを判定するための関数を以下のように改良します。"price":0}ここでは、過去のローソク足データ(last_data)を30個丁度にキープするのではなく、どんどん溜めていって、後ろから欲しい数だけを取り出すようにしています。後ろから20コを取り出すようにしています。ここで新しく使っているのが、スライス(:)という記述方法です。スライスとはスライスは、配列データの範囲を指定したり、pandasの表データで行や列の範囲を指定するときに、非常に便利な記述方法です。以下のように書くことで、配列や表(行・列)の範囲を指定することができます。後ろから3番目~最後までプログラミングの世界では、先頭は0番目から数えるので、指定した数字から1つズレる点に注意してください。7]そのため、上値ブレイクアウトの判定に必要な期間分のローソク足データは、以下のように指定することができます。]この知識を使うと、ブレイクアウトの判定に必要な期間のデータを取り出しその中から最大値を抽出する、というコードを以下の1行で書くことができます。])2.ブレイクアウトの判定基準に「終値」を使えるようにするこれは簡単ですね。指定していた部分を変数にすればいいだけです。最後にメイン処理のWhile文ループの中身を修正します。両方の期間分を超えるまでデータを蓄積します。変更後(削除)またスライスを使うことで、過去のローソク足データをぴったりn個にキープしておく必要がなくなったので、上記の行は削除しておきます。これで完成です!あとはログファイルの出力が「直近の安値が~」「直近の高値が~」になっているので、そこは各自で修正しておいてください。試しに1時間足で、買いエントリーは30期間の最高値ブレイクアウト、売りエントリーは20期間の最安値ブレイクアウト、各ブレイクアウトの判定には終値を使う、という場合の成績を見てみましょう。ここでの月間ドローダウンは、その月に経験している継続中の最大ドローダウンのことです。その月だけのドローダウンを計算しているわけではありません。このように、同じドンチャン・ブレイクアウトのロジックでも、買い判定の期間、売り判定の期間、ブレイクアウトの判定を高値(安値)でするか、終値でするか、などの設定によってかなり異なる結果になります。どのようなパラメーター設定が最適なのかを手作業で探すのは困難です。以下のようなパラメーター設定だけでも、その組み合わせの数は膨大だからです。上記の組み合わせを試すだけでも、そのパターンは640通りにもなります。コメントを残すPandasを使ってBTCFXの自動売買BOTの月別の成績を集計しよう!前回の記事では、ドンチャンブレイクアウトの自動売買BOTの成績を、matplotlibを使って視覚的なグラフにする方法や、最大ドローダウンを計算する方法を説明しました。自動売買BOTの安定性を評価するためには、最終的な損益だけでなく途中の経過、つまり月別の成績を把握しておくことも必須です。前回までの記事では、なるべくpythonのプログラミング初心者でもわかりやすいように、すべてのトレード成績を配列に記録していました。0,"date":[],"gross-profit":[0],"slippage":[],"log":[]}}しかしこのやり方だと、買いエントリーと売りエントリーに分けて成績を記録しているため、違う切り口でデータを集計したいときに凄く不便です。「売り・買いで分けずに、月別の平均リターンや勝率を検証したい」と思った場合、売り・買いそれぞれのデータを(順番がおかしくならないように)くっつけて、それを日付で分割する、というややこしい処理をしなければなりません。新しく月別の勝率を記録する変数を用意する方法もありますが、そうするとflag変数がどんどん増えていってしまい、pythonコードがどんどん長くややこしくなります。このようなことは、Excelシートだと簡単にできますよね。同じことがpythonでも出来ないでしょうか?実は、全く同じようなことがpythonでできるライブラリがあります。それがPandasデータフレームです。Pandasは表計算ソフトのようなものPythonを勉強しはじめた初心者の方でも、「Pandas」という言葉を聞いたことがあるかもしれません。「なんだか上級者向きの難しい奴でしょ?」というイメージをお持ちの方も多いかもしれません。Pandasは全く難しくありません。要するに「Excelのような表計算ソフトと同じようなものだ」と思えば、文系の方でも少し親近感が湧きますよね。今回の記事でpandasの使い方をマスターしてしまいましょう!Pandasの使い方をマスターする1)まずは表型のデータを作ってみよう!pandasでは、複数行・複数列にわたる表形式のデータのことを「DataFrame」といいます。練習としてDataFrame型の変数を1つ作ってみましょう!今回は、以下のような5回分のトレード成績を記録した配列を用意しました。これは先ほど表の例と全く同じものです。今回はこちらを例に説明していきます。表を作るために必要な最低限の情報を記録した配列を用意します。「トレード回数」「ドローダウン」「総損益」「買いエントリーの勝率」など、後から表をもとに計算できる数字は、この時点で準備する必要はありません。トレードするたびに記録する数字は、上記の5種類だけでOKということです。すべてのトレードが終わったら、上記の配列をくっつけて1つのDataFrame型の表データに変換します。それが以下のコードです。いう名前の変数に入れています。これを公式にすると、以下のような感じですね。データ配列})日付データだけは、pd.to_datetime()を使って、テキスト型から日付型に変換しておきます。その理由は、前回の記事で解説しています。)以下のようになります。欲しい数字だけを集計して取り出そう!では、次にこの表データ(records)から「欲しい数字」を取り出していきましょう。以下、よく使うパターンをまとめて列挙していきます。ように「表名.列名」指定できます。かたちで表データの範囲を指定する記述方法です。]のかたちで表データ範囲を指定する記述方法です。これらは以下のサイトに詳しい説明があります。