
知っておくべきMQL5ウィザードのテクニック(第19回):ベイズ推定
はじめに
引き続きMQL5ウィザードを利用して、統計学の手法の1つで、新しい情報が入るたびに確率を処理し更新するベイズ推定を確認します。その応用範囲は明らかに広範囲にわたりますが、トレーダーとしての私たちの目的として、時系列の予測におけるその役割に焦点を当てます。トレーダーが分析できる時系列は、主に取引されている証券の価格ですが、この記事で見るように、これらの時系列を「拡張」して、証券の取引履歴のような代替物を考慮することもできます。
理論的には、ベイズ推論は、あらゆる仮説の再評価を内在させるため、あらゆる取引システムの市場適応性を高めるはずです。これは、過去のデータでテストし、その後フォワードウォークや実際の口座でのテストをおこなう際の、曲線あてはめの数を減らすことにつながるはずです。ただし、それは理論上のことであり、実際には実装によって健全なアイデアが台無しになることもあります。だからこそ、この記事ではベイズ推定の実装について複数の可能性を考えてみることにします。
この記事は、ベイズ推定の定義、カスタムシグナルクラス、資金管理クラス、トレーリングストップクラスの解説をカバーする応用例、戦略テスト報告、そして最後に結論というシンプルな形式で構成されています。
定義
ベイズ推定(BI)は、式P(H|E) = [P(E|H) * P(H)] / P(E)によって支えられています。ここで、
- Hは仮説
- Eは証拠
- P(H)は仮説の事前確率
- P(E)は証拠確率(別名、周辺尤度)
- P(H|E)とP(E|H)は上記それぞれの条件付き確率(それぞれ別名事後確率、尤度)
上記の式はシンプルでわかりやすいですが、鶏と卵のような問題があります。つまり、P(E|H)をどのように見つけるかということです。というのも、上に挙げた式は、その解を暗示しているからです。
P(E|H) = [P(H|E) * P(E)] / P(H)
ただし、これはP(E|H) = [P(E∩H)] / P(H) と書き換えることもできます。後述するように、この状況では手動で回避策を講じることができます。
シグナルクラス
シグナルクラスは通常、EAが取るべきポジション(ロングかショートか)を決定します。これは指標の重み付けを加算することでおこなわれ、合計値は常に0から100の範囲になります。BIを使用する際には様々な時系列の選択がありますが、本稿ではシグナルクラスのため、終値変化時系列のみを用いて説明します。
この時系列あるいは他の時系列を使用するには、まず時系列の値を分類するかクラスタリングするかの「体系的」な方法を見つける必要があります。この明らかなステップは重要です。時系列データを正規化するだけでなく、その確率を処理する際に適切に識別できるようにするためです。
クラスタリングは教師なしでおこなわれます。ここではその初歩的なアプローチを使用し、価格変動のタイプに応じて各データポイントにクラスタを割り当てます。すべての正の値、ゼロの値、負の値にはそれぞれクラスタが割り当てられます。本連載では過去に別のクラスタリングアプローチを検討したことがあります。それらをお試してになってもいいですが、今回はクラスタリングが主要なテーマではないので、ごく初歩的なことを検討しました。
この基本的なクラスタリングアプローチでも、データ点をよりよく「識別」し、その確率を評価できることは明らかです。これがなければ、データは浮動小数点であるため、各データは一意であり、要するに一意なクラスタタイプとなり、確率値を得て計算するという私たちの目的が明らかに損なわれることになります。私たちのシンプルなアプローチは以下のソースに実装されています。
//+------------------------------------------------------------------+ //| Function to assign cluster for each data point | //+------------------------------------------------------------------+ void CSignalBAYES::SetCluster(matrix &Series) { for(int i = 0; i < int(Series.Rows()); i++) { if(Series[i][0] < 0.0) { Series[i][1] = 0.0; } else if(Series[i][0] == 0.0) { Series[i][1] = 1.0; } else if(Series[i][0] > 0.0) { Series[i][1] = 2.0; } } }
データ点を「特定」したら、上の式で定義されているように事後確率を計算します。しかし、そうするためには、仮説となる特定のクラスタタイプが必要になります。このクラスタは、ロングポジションとショートポジションで一意でなければならないため、それぞれの場合で使用するクラスタタイプを特定する指標となるカスタム入力パラメータを用意します。それぞれm_cluster_longとm_cluster_shortです。
したがって、事後確率を求めるには、このクラスタインデックスと「識別された」あるいはクラスタ化された時系列が入力として必要となります。事後確率を計算する関数は、現在のクラスタタイプが与えられたときに、ポジションタイプのクラスタが発生する確率を求めます。私たちは一連の最近のデータポイントを提供しており、それぞれが行列形式でクラスタインデックスを持つので、基本的に現在のクラスタはゼロインデックスのデータポイントです。
上記の鶏と卵のような状況を解決するために、P(E|H)を計算します。
第一原理から。上記で説明したように、Hはそれぞれの位置インデックスで表されるため、証拠Eは入力系列内のインデックスゼロの現在のクラスタまたはクラスタタイプです。つまり、事後確率は、最新の証拠(インデックス0のクラスタ)が発生した場合に、与えられた位置のクラスタタイプが次に発生する可能性を見つけることです。
したがって、P(E|H)を逆に求めるには、入力系列を再検討し、位置インデックスHがゼロインデックスE(証拠)に続いて発生したときの列挙をおこないます。これも確率なので、まず空間を列挙し、H個の出現を見つけ、次にその空間の中で、証拠となる指標が何回連続して続くかを見つけることになります。
このことは、私たちの入力系列が、検討中のクラスタタイプの数に応じて、十分な長さであることを明らかに示唆しています。この非常に単純な例では、3つのクラスタタイプ(ゼロ価格変動がめったに起こらないことを考慮すると、実際には2つ)があり、50未満の入力系列で機能するでしょう。しかし、5、6種類以上のクラスタを使用するような、より冒険的なクラスタリングアプローチを選択する場合、事後関数が機能するためには、入力系列のデフォルトサイズはこれらすべてのクラスタタイプの発生を捕捉するのに十分な大きさである必要があります。事後関数のコードは以下の通りです。
//+------------------------------------------------------------------+ //| Function to calculate the posterior probability for each cluster | //+------------------------------------------------------------------+ double CSignalBAYES::GetPosterior(int Type, matrix &Series) { double _eh_sum = 0.0, _eh = 0.0, _e = 0.0, _h = 0.0; for(int i = 0; i < int(Series.Rows()); i++) { if(Type == Series[i][1]) { _h += 1.0; if(i != 0) { _eh_sum += 1.0; if(Series[i][1] == Series[i - 1][1]) { _eh += 1.0; } } } if(i != 0 && Series[0][1] == Series[i][1]) { _e += 1.0; } } _h /= double(Series.Rows() - 1); _e /= double(Series.Rows() - 1); if(_eh_sum > 0.0) { _eh /= _eh_sum; } double _posterior = 0.0; if(_e > 0.0) { _posterior += ((_eh * _h) / _e); } return(_posterior); }
事後確率を得ると、それは現在のクラスタタイプ(すなわち、インデックスゼロのデータポイントに対する証拠またはクラスタタイプ)が与えられたときに、その位置の最適なクラスタタイプ(m_cluster_longであれm_cluster_shortであれ)が発生する可能性を表します。これは0.0から1.0の範囲の値となります。ロングポジションであれショートポジションであれ、それぞれの仮説が確度の高いものであるためには、返される値は理想的には0.5以上でなければなりません。ただし、わずかに低い値で興味深い結果が得られる可能性がある特別な状況はご自分で調査してみてください。
この小数値は、LongCondition関数とShortCondition関数が出力する標準的な0~100の範囲に正規化する必要があります。そのためには、単純に100.0を掛けます。LongCondition関数とShortCondition関数の典型的なコードを以下に示します。
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalBAYES::LongCondition(void) { int result = 0; vector _s_new, _s_old, _s; _s_new.CopyRates(m_symbol.Name(), m_period, 8, 0, m_series_size); _s_old.CopyRates(m_symbol.Name(), m_period, 8, 1, m_series_size); _s = _s_new - _s_old; matrix _series; _series.Init(_s.Size(), 2); for(int i = 0; i < int(_s.Size()); i++) { _series[i][0] = _s[i]; } SetCluster(_series); double _cond = GetPosterior(m_long_cluster, _series); _cond *= 100.0; //printf(__FUNCSIG__ + " cond: %.2f", _cond); //return(result); if(_cond > 50.0) { result = int(2.0 * (_cond - 50.0)); } return(result); }
このシグナルクラスを使用すると、簡単にMQL5ウィザードで任意のEAに組み込むことができます。MQL5ウィザードを初めて使用する読者はこちらとこちらのガイドをご覧ください。
資金管理クラス
BIを利用したカスタムの資金管理(MM)クラスを実装することもできます。まず分析の基礎となる適切な時系列を選択する必要があります。繰り返しになりますが、冒頭で述べたように、このMMでは過去の取引実績を選択します。つまり、ウィザードを組み込んだEAは1つの銘柄だけを取引するので、クエリで選択可能な取引履歴はすべてEAに適用されます。
取引履歴の時系列を分析の基礎として活用するにあたっては、内蔵されている資金管理クラスの1つであるCMoneySizeOptimizedを参考にします。このクラスでは、取引量のサイズを直近の連敗数に比例して小さくします。ただし私たちの場合、もし好みのクラスタインデックス(仮説)の尤度が、m_conditionと呼ばれる別の最適化可能なパラメータを下回れば、サイズのロットを減らすことになります。
つまり、ここで本質的に確立しようとしているのは、余剰証拠金に比例して通常のロットサイジングを使用できる理想的なクラスタ指数です。このクラスタインデックスは、余剰証拠金に比例してロットサイズを自由に変更できるエクイティカーブ(1つの銘柄しか取引されないため)の種類を示す識別子です。「エクイティカーブのタイプ」への言及は少々広範囲です。私たちのクラスタリングがシグナルクラスで採用した単純な形式に従っているためです。ここで具体的に指摘されているのは、取引結果のタイプ、つまり勝ちか負けかです(利益ゼロの結果にはインデックスが割り当てられますが、分析で大きく取り上げられる可能性は低いです)。
これは、例えば、余剰証拠金によるロットサイズの拡大縮小が有利な取引結果である場合、過去の取引結果のシーケンスを検証し、証拠(入力シリーズのゼロインデックスでの取引結果)に照らして、別の有益な取引結果を得る可能性を確立しようとすることを意味します。
このような場合、目標とする好条件が繰り返される可能性を測る確率の閾値という形で、別の最適化可能なパラメータが必要になります。事後結果がこの閾値を下回った場合、元の「最適化されたサイズ」の資金管理クラスの場合と同様に、カウントされた損失数に比例してポジションサイジングが縮小されます。最適化関数のコードは以下の通りです。
//+------------------------------------------------------------------+ //| Optimizing lot size for open. | //+------------------------------------------------------------------+ double CMoneyBAYES::Optimize(int Type, double lots) { double lot = lots; //--- calculate number of losses orders without a break if(m_decrease_factor > 0) { //--- select history for access HistorySelect(0, TimeCurrent()); //--- int orders = HistoryDealsTotal(); // total history deals int losses=0; // number of consequent losing orders //-- int size=0; matrix series; series.Init(fmin(m_series_size,orders), 2); series.Fill(0.0); //-- CDealInfo deal; //--- for(int i = orders - 1; i >= 0; i--) { deal.Ticket(HistoryDealGetTicket(i)); if(deal.Ticket() == 0) { Print("CMoneySizeOptimized::Optimize: HistoryDealGetTicket failed, no trade history"); break; } //--- check symbol if(deal.Symbol() != m_symbol.Name()) continue; //--- check profit double profit = deal.Profit(); //-- series[size][0] = profit; size++; //-- if(size >= m_series_size) break; if(profit<0.0) losses++; } //-- series.Resize(size,2); SetCluster(series); double _cond = GetPosterior(Type, series); //-- //--- if(_cond < m_condition) lot = NormalizeDouble(lot - lot * losses / m_decrease_factor, 2); } //--- normalize and check limits ... //--- ... //--- ... //--- return(lot); }
減少係数と投資された証拠金の割合の他のすべてのパラメータは、元のCMoneySizeOptimized MMクラスと同じままです。
BIとの比較のために、ケリーの基準(英語)を考慮することができます。これは、勝率とリスクリワードレシオを考慮しますが、長期的な視野に立ち、必ずしも直近または中間のパフォーマンスによって配分基準を更新するものではありません。その式は、K = W - ((1 - W) / R)で与えられます。
ここで
- Kは配分率
- Wは勝率
- Rはプロフィットファクター
このアプローチは、長期的展望をもって資本を配分することから、投資の達人たちに採用されてきたと言われていますが、長期的なアプローチをとるべきはポジショニングであって、配分ではないと主張することもできます。長期的な展望はしばしば経営に採用されますが、リスクが伴う場合には短期的な展望がより重要になる傾向があるため、実行は別の主題となります。
つまり、ケリー基準(KC)に対するBIの優位性は、KCが市場における一定のエッジを想定しているという議論に集約されます。これは、非常に長い期間を想定する場合に当てはまる場合があります。取引コストとスリッページを無視することも、KCに対する似たような反論です。どちらも超長期的には無視できるかもしれませんが、ほとんどの市場の仕組みは、誰かが誰かに代わって、あるいは誰かの資本で取引できるようになっていると言っていいでしょう。このことは本質的に、これらの短期的な変動に対して相当の注意を払う必要があることを意味します。なぜなら、これらの変動によって、トレーダーや投資家が依然として運用中の資本を託されているかどうかが決まる可能性があるからです。
トレーリングストップクラス
最後に、BIを利用したカスタムトレーリングクラスの実装を見てみましょう。この時系列は、常にボラティリティの良い代理であり、ポジションのストップロスレベルをどの程度調整すべきかを左右する重要な指標であるため、価格バーの範囲に注目する必要があります。シグナルには終値の変化を使用して、MMには取引結果(口座資本レベルとは対照的な利益)を使用しましたが、これも事実上の口座エクイティレベルの変化です。この変更を初歩的なクラスタリング手法に適用すると、浮動小数点データポイントのグループ化に役立つ、非常に基本的だが実用的なインデックスのセットが得られます。
ExpertTrailingクラスのための同様のアプローチは、高値から安値までのバー範囲の変化に焦点を当てるでしょう。このアプローチに関する私たちの仮説は、クラスタインデックス(このトレーリング状況ではm_long_clusterとm_short_clusterは両方とも同じである可能性がある)を探しているため、時系列でそれが続く可能性が高くなると、現在の価格バーの範囲に比例した量だけストップロスを移動する必要があるというものです。
ロングポジションとショートポジションに別々の入力パラメータを使用しましたが、原理的には、ロングポジションとショートポジションの両方のストップロス調整に対応するために、1つの入力パラメータを使用することも可能です。これをロングポジションの場合に実装したコードを以下に示します。
//+------------------------------------------------------------------+ //| Checking trailing stop and/or profit for long position. | //+------------------------------------------------------------------+ bool CTrailingBAYES::CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp) { //--- check ... //--- ... //--- sl=EMPTY_VALUE; tp=EMPTY_VALUE; // vector _h_new, _h_old, _l_new, _l_old, _s; _h_new.CopyRates(m_symbol.Name(), m_period, COPY_RATES_HIGH, 0, m_series_size); _h_old.CopyRates(m_symbol.Name(), m_period, COPY_RATES_HIGH, 1, m_series_size); _l_new.CopyRates(m_symbol.Name(), m_period, COPY_RATES_LOW, 0, m_series_size); _l_old.CopyRates(m_symbol.Name(), m_period, COPY_RATES_LOW, 1, m_series_size); _s = (_h_new - _l_new) - (_h_old - _l_old); matrix _series; _series.Init(_s.Size(), 2); for(int i = 0; i < int(_s.Size()); i++) { _series[i][0] = _s[i]; } SetCluster(_series); double _cond = GetPosterior(m_long_cluster, _series); // delta=0.5*(_h_new[0] - _l_new[0]); if(_cond>0.5&&price-base>delta) { sl=price-delta; } //--- return(sl!=EMPTY_VALUE); }
MQL5ライブラリに内蔵されているような別のトレーリングストップクラスとの比較は、以下のテストとレポートのセクションで提供されています。
テストとレポート
4時間足で2022年のEUR JPYのテストをおこないます。EAウィザードで使用可能な3つのカスタムクラスを開発したので、3つのEAを順次組み立てます。最初のEAはシグナルクラスのみを持ち、資金管理は固定ロットを利用し、トレーリングストップは使用しません。2番目は同じシグナルクラスを持ちますが、上でコーディングした資金管理クラスが追加され、トレーリングストップはなくなります。一方、最後のEAには、上でコーディングした3つのクラスがすべて含まれます。ウィザードを使用したこれらのクラスの組み立てに関するガイドラインはこちらにあります。
3つのEAすべてでテストを実行すると、次のようなレポートとエクイティカーブが得られます。
BIシグナルクラスのみを使用したEAのレポートとエクイティカーブ
BIシグナルクラスとMMクラスのみを使用したEAのレポートとエクイティカーブ
BIシグナルクラス、MMクラス、トレーリングクラスを使用したEAのレポートとエクイティカーブ
シグナルクラスからMMを経てトレーリングクラスへのBIの適応が進むにつれて、全体的な成績は正の相関を示す傾向があるようです。このテストは実際のティックでおこなわれましたが、いつものように、より長い期間にわたる独立したテストが理想的であることを念頭に置いてください。コントロールとして、ライブラリクラスを使用する3つのEAを最適化することができます。これらのテストでは、エグジット価格目標は使用せず、エグジットをコントロールするためにオープンシグナルとクローズシグナルのみに頼っています。「コントロール」EAで使用するものとして、awesome oscillatorシグナルクラス、CMoneySizeOptimized資金管理クラス、移動平均トレーリングクラスを選びます。上記と同様のテストをおこなったところ、以下のような結果が得られました。
awesome oscillatorクラスのみを使用したEAのレポートとエクイティカーブ
2つのクラスを選択したコントロールEAのレポートとエクイティカーブ
選択された3つのクラスすべてを使用したコントロールEAのレポートとエクイティカーブ
私たちのコントロールのパフォーマンスは、BIエキスパートに遅れをとっていますが、例外は3つ目です。代替シグナル、MM、トレーリングクラスの選択もこの「結果」に大きく影響しましたが、全体的な目標は、BI EAとMQL5ライブラリからすぐに利用できるものとの間にパフォーマンスに大きな違いがあるかどうかを確認することであり、その答えは明らかです。
結論
結論として、MQL5ウィザードのEAを組み立てる3つの異なる柱となるクラスにベイズ推定の基本的な考え方を組み込むことで、シンプルなEAを構築する上でベイズ推定が果たす役割を検証しました。ここでのアプローチはあくまでも入門的なものであり、特に、より複雑なクラスタアルゴリズムや多次元データセットの使用に関連する重要な分野については扱っていません。これらはすべて探求可能な手段であり、良質なティックデータで適切な履歴期間にわたって適切にテストすれば、優位に立てる可能性があります。ベイズ推定のもとでは、さらに多くのことをテストすることが可能です。ウィザードで組み立てたEAがアイデアをテストし、プロトタイプを作成する上で信頼できるツールであることに変わりはないので、ご自身でご探求ください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14908





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索