知っておくべきMQL5ウィザードのテクニック(第03回):シャノンのエントロピー
1.0 はじめに
クロード・シャノンは1948年に、情報エントロピーの斬新な理想を持った論文「通信の数学的理論」を発表しました。エントロピーは物理学からの概念で、物質内の粒子がアクティブである範囲の尺度です。たとえば、水の3つの状態、つまり氷、液体、蒸気を考えると、粒子の運動エネルギーは蒸気で最も高く、氷で最も小さいことがわかります。これと同じ概念が、確率を介して数学に適用されます。次の3つのセットを検討してください。
セット1:
セット2:
セット3:
これらのセットのどれが最高のエントロピーを持つかを推測するとしたら、どちらでしょうか。
最後を選んだ方は正しいですが、その答えをどのように検証するのでしょうか。これに答える最も簡単な方法は、各セットを再編成できる方法の数をエントロピー推定値として取り、同様の色のストレッチを無視することです。したがって、最初のセットを「再配置」する方法は1つしかありませんが、後のセットを見ると、明らかに色に関する順列の数が大幅に増加しているため、最後のセットが最も高いエントロピーを持っていると主張できます。
情報に基づいてエントロピーを決定するより正確な方法があります。セット1から任意にボールを選ぶとしたら、このボールを選ぶ前に、そのボールについて何がわかるでしょうか。セットには青いボールのみが含まれているため、青いボールであることを確信できます。したがって、セット1から何を選択するかについて完全な情報を持っていると言えます。セット2とセット3を考慮すると、情報はそれぞれますます完全ではなくなります。したがって、数学では、エントロピーは情報に反比例します。エントロピーが高いほど、未知数が多くなります。
では、エントロピーを計算する式をどのように導き出すのでしょうか。セットについてもう一度考えると、セット内のボールの種類が多様であるほど、エントロピーは高くなります。ボールを選択する前にボールに関する情報を測定する上で最も敏感な要素は、セット内の各ボールを選択する確率です。したがって、各セット全体を検討し、各ボールの選択が独立したイベントであることを前提として、セットに表示される頻度と同じ頻度ですべての色が選択された8つのボールを選択する可能性を見つけるとします。その場合、各セットの確率(したがって「既知の情報」)は、各ボールの個々の確率の積になります。
セット1:
(1.0 x 1.0 x 1.0 x 1.0 x 1.0 x 1.0 x 1.0 x 1.0) = 1.0
セット2:
(0.625 x 0.625 x 0.625 x 0.625 x 0.625 x 0.375 x 0.375 x 0.375) ~ 0.0050
セット3:
(0.375 x 0.375 x 0.375 x 0.25 x 0.25 x 0.25 x 0.25 x 0.125) ~ 0.000025
セット1:
-(8 x 0.125 x log2(1.0)) = 0.0
セット2:
(-(0.625 x log2(0.625)) - (0.375 x log2(0.375))) ~ 0.9544
セット3:
(- (0.375 x log2(0.375)) - (2 x 0.25 x log2(0.25)) - (0.125 x log2(0.125))) ~ 1.906
2.0 クラスの作成
この記事では、MQL5ライブラリのデシジョンフォレストクラスを使用します。具体的には、シャノンのエントロピーシグナルの有効性を調べる際に、ランダムフォレストのアイデアを抽象化します。この記事はランダムフォレストに関するものではなく、シャノンのエントロピーに関するものです。
デシジョンフォレストクラスはランダムフォレストモデルの基礎となるため、再度検討してみましょう。これは良いことです。デシジョンフォレストは生得的なものです。私たちは皆、意識しているかどうかにかかわらず、ある時点で決定木を使用しているため、その概念自体は奇妙ではありません。
どのように機能するかをよりよく説明するために、例を見てみましょう。
データセットが上記の価格バーで構成されているとします。3つの下降ローソク足と5つの上昇ローソク足(弱気市場と強気市場をクラスとして取ります)があり、それらの属性を使用してクラスを分離したいと考えています。属性は、価格の方向性と頭と尻尾の長さの比較です。これらは方向の変化の前兆となる可能性があるためです。どのようにすればいいでしょうか。
白は弱気のローソク足を表し、青は強気を表すため、価格の方向性は単純な属性のように見えます。したがって、「価格は下落していますか?」という質問を使用して、最初のジャンクションを分離します。枝が2つに分かれる点としての木のジャンクションは、「Yes」分岐と「No」分岐を選択する基準を満たします。
No分岐(上昇ローソク足)はすべて頭よりも尾が長いため、そこで終了ですが、Yes分岐ではそうではないため、さらに作業が必要です。2番目の属性を使用して、「頭は尾よりも長いですか?」と尋ねます。2番目の分割をおこないます。
頭が長い2つの下降ローソク足はYesサブブランチの下にあり、尾が長い1つの下降ローソク足は右サブブランチの下に分類されます。この決定木では、2つの属性を使用して、基準に従ってデータを完全に分割することができました。
実際の取引システムでは、ティックボリューム、時刻、およびその他の多くの取引指標をすべて使用して、より包括的な意思決定木を構築できます。それにもかかわらず、ノードでの各質問の中心的な前提は、上記のデータを2つの異なるセットに分割(または分類)し、各セットの作成されたメンバーが類似している1つの属性を見つけることです。
ランダムフォレストに移行すると、その名前が示すように、グループとして機能する膨大な数の個々の決定木で構成されます。ランダムフォレスト内のすべての木に対してクラス予測がおこなわれ、投票数が最も多いクラスがモデルの予測として選択されます。 ランダムフォレストの背後にある主な概念は見落としがちですが、非常に強力な概念、つまり大衆の知恵です。予測をおこなう際、無相関木は、個々の木がどれほど多くのデータで訓練されていても、個々の木よりも優れています。低相関が核心です。この理由は、Tonによると、木が個々のエラーからお互いを保護するためです(すべてのエラーが常に同じ方向に進まない限り)。ランダムフォレストが機能するには、予測シグナルが平均よりも優れている必要があり、各木のパーディション/エラーが互いに低い相関関係にある必要があります。
例として、2つの取引システムがあり、最初の取引システムでは1年間に最大1000ドルの証拠金注文を出すことができ、もう1つの取引システムでは1000ドルの証拠金注文を1回だけ出すことができるとします。期待が同様だと仮定したらどちらを好まれるでしょうか。ほとんどの人が好むのは、トレーダーに「より多くの制御」を与える最初のシステムです。
では、ランダムフォレストアルゴリズムはどのようにして、個々の木の特性がフォレスト内の他の木の特性と相関しすぎないようにするのでしょうか。それは、次の2つの特徴に集約されます。
2.0.1 バギング
決定木は訓練データの影響を非常に受けやすく、わずかな変更でもフォレストが大きく異なる可能性があります。 ランダムフォレストは、置換中に各木がデータセットをランダムにサンプリングすることでこれを使用し、異なる木を生成します。このプロセスは、ブートストラップ集約の略語であるバギングとして知られています。
バギングでは、訓練データを小さなセットに置き換えるのではなく、元の訓練データの代わりに、サイズNのランダムサンプルをいくつかの置換で取得することに注意してください。初期設定サイズNが維持されます。たとえば、訓練データが[U、V、W、X、Y、Z]の場合、木の1つに次のリスト[U、U、V、X、X、Z]を与えることができます。両方のリストで、サイズN(6)が保持され、「U」と「X」の両方がランダムに選択されたデータで繰り返されます。
2.0.2 特徴のランダム性
通常、決定木では、ノードの分割には、考えられるすべての特徴を考慮し、最終的な左ノードの観測値と最終的な右ノードの観測値の違いが最も大きくなるものを選択する必要があります。一方、ランダムフォレストでは、各木は特徴のランダムなサブセットからのみ選択できます。これにより、フォレスト内でより多くのバリエーションが強制される傾向があり、最終的には木間の相関関係が低くなり、多様化が進みます。
視覚的な例を見てみましょう。上の図では、ノードを分割する方法を決定する際に、従来の決定木(青色)が4つの特徴すべてから選択できます。データを可能な限り分離されたグループに分割するため、特徴1(黒と下線付き)を使用することにしました。
では、ランダムフォレストを見てみましょう。この例では、森の2本の木だけを調べます。ランダムフォレストの木1をチェックアウトすると、ノード分割の決定には(ランダムに選択された)特徴2と3しか考慮できないことがわかります。従来の決定木(青)から、特徴1が分割に最適な特徴であることがわかっていますが、木1には特徴1が表示されないため、特徴2(黒と下線)を使用する必要があります。一方、木2は特徴1と3しか表示できないため、特徴1を選択できます。
そのため、ランダムフォレストでは、木はデータセットで訓練されるだけでなく(バギングのおかげで)、決定を下す際にさまざまな特徴も使用します。
ランダムフォレストを使用することで、EAは過去の取引結果と市場シグナルを処理して、売買の決定に到達します。エントロピーからスクリーナーだけでなくシグナルを取得する際に、最近のセット内の正の価格バーのエントロピーと負の価格バーのエントロピーを別々に検討します。
ランダムフォレストの構築と訓練は、単一のスレッドによる最適化でのみおこなわれます。
2.1 EAシグナルクラス
// wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Signals of'Shannon Entropy' | //| Type=SignalAdvanced | //| Name=Shannon Entropy | //| ShortName=SE | //| Class=CSignalSE | //| Page=signal_se | //| Parameter=Reset,bool,false,Reset Training | //| Parameter=Trees,int,50,Trees number | //| Parameter=Regularization,double,0.15,Regularization Threshold | //| Parameter=Trainings,int,21,Trainings number | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| Class CSignalSE. | //| Purpose: Class of generator of trade signals based on | //| the 'Shannon Entropy' signals. | //| Is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSignalSE : public CExpertSignal { public: //Decision Forest objects. CDecisionForest DF; //Decision Forest CMatrixDouble DF_SIGNAL; //Decision Forest Matrix for inputs and output CDFReport DF_REPORT; //Decision Forest Report for results int DF_INFO; //Decision Forest feedback double m_out_calculations[2], m_in_calculations[__INPUTS]; //Decision Forest calculation arrays //--- adjusted parameters bool m_reset; int m_trees; double m_regularization; int m_trainings; //--- methods of setting adjustable parameters void Reset(bool value){ m_reset=value; } void Trees(int value){ m_trees=value; } void Regularization(double value){ m_regularization=value; } void Trainings(int value){ m_trainings=value; } //Decision Forest FUZZY system objects CMamdaniFuzzySystem *m_fuzzy; CFuzzyVariable *m_in_variables[__INPUTS]; CFuzzyVariable *m_out_variable; CDictionary_Obj_Double *m_in_text[__INPUTS]; CDictionary_Obj_Double *m_out_text; CMamdaniFuzzyRule *m_rule[__RULES]; CList *m_in_list; double m_signals[][__INPUTS]; CNormalMembershipFunction *m_update; datetime m_last_time; double m_last_signal; double m_last_condition; CSignalSE(void); ~CSignalSE(void); //--- method of verification of settings virtual bool ValidationSettings(void); //--- method of creating the indicator and timeseries virtual bool InitIndicators(CIndicators *indicators); //--- methods of checking if the market models are formed virtual int LongCondition(void); virtual int ShortCondition(void); bool m_random; bool m_read_forest; int m_samples; //--- method of initialization of the oscillator bool InitSE(CIndicators *indicators); double Data(int Index){ return(Close(StartIndex()+Index)-Close(StartIndex()+Index+1)); } void ReadForest(); void WriteForest(); void SignalUpdate(double Signal); void ResultUpdate(double Result); double Signal(void); double Result(void); bool IsNewBar(void); };
2.1.1 シグナル
このエントロピーは、最新性のためにインデックスによって重み付けされます。
if(_data>0.0) { _long_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); } else if(_data<0.0) { _short_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); }
また、価格バーの大きさによって重み付けされます。
if(_data>0.0) { _long_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); } else if(_data<0.0) { _short_entropy-=((1.0/__SIGNALS[i])*((__SIGNALS[i]-s)/__SIGNALS[i])*(fabs(_data)/_range)*(log10(1.0/__SIGNALS[i])/log10(2.0))); }
生成されるシグナルは、負のバーのエントロピーから正のバーのエントロピーを引いたものになります。ここでの理由は、負のバーのエントロピーが正のバーのエントロピーを超える場合、負のバーに関する情報が少ないため、同様にロングポジションを意味する正のバーの場合よりもショートポジションになるからです。表面的には、これは「危険な」トレンドフォローシステムになるように見えます。ただし、エントロピーを計算する際に上記の重み付けが適用されるため、そうではない場合があります。
ポジションが開かれることを意味するため、ロングまたはショートの状態がオープンしきい値を超えると、シグナルが更新されます。これはタイマーで発生するため、これに対応するようにウィザードアセンブルEAを変更します。
//+------------------------------------------------------------------+ //| "Timer" event handler function | //+------------------------------------------------------------------+ void OnTimer() { if(PositionSelect(Symbol()) && Signal_ThresholdClose<=fabs(filter0.m_last_condition)) { filter0.ResultUpdate(filter0.Result()); } // if(!PositionSelect(Symbol()) && Signal_ThresholdOpen<=fabs(filter0.m_last_condition)) { filter0.SignalUpdate(filter0.m_last_signal); } ExtExpert.OnTimer(); }
クラスのソース関数は、既に述べたように最適化でのみ実行されます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSignalSE::SignalUpdate(double Signal) { if(MQLInfoInteger(MQL_OPTIMIZATION)) { m_samples++; DF_SIGNAL.Resize(m_samples,__INPUTS+2); for(int i=0;i<__INPUTS;i++) { DF_SIGNAL[m_samples-1].Set(i,m_signals[0][i]); } // DF_SIGNAL[m_samples-1].Set(__INPUTS,Signal); DF_SIGNAL[m_samples-1].Set(__INPUTS+1,1-Signal); } }
2.1.2 結果
結果は、最後に閉じたポジションの利益に基づきます。
if(HistorySelect(0,m_symbol.Time())) { int _deals=HistoryDealsTotal(); for(int d=_deals-1;d>=0;d--) { ulong _deal_ticket=HistoryDealGetTicket(d); if(HistoryDealSelect(_deal_ticket)) { if(HistoryDealGetInteger(_deal_ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT) { _result=HistoryDealGetDouble(_deal_ticket,DEAL_PROFIT); break; } } } } return(_result);
また、ポジションが決済されることを意味するため、ロングまたはショートの条件が決済しきい値を超えると更新されます。これも上記のように、クラス関数が最適化時にデシジョンフォレストファイルのみを更新するタイマーで発生します。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSignalSE::ResultUpdate(double Result) { if(MQLInfoInteger(MQL_OPTIMIZATION)) { int _err; if(Result<0.0) { double _odds = MathRandomUniform(0,1,_err); // DF_SIGNAL[m_samples-1].Set(__INPUTS,_odds); DF_SIGNAL[m_samples-1].Set(__INPUTS+1,1-_odds); } } }
2.1.3 フォレストの書き込み
フォレストは読み取り前にティックで書き込まれるため、これに対応するようにウィザードアセンブルEAを変更します。
//+------------------------------------------------------------------+ //| "Tick" event handler function | //+------------------------------------------------------------------+ void OnTick() { if(!signal_se.m_read_forest) signal_se.WriteForest(); ExtExpert.OnTick(); }
2.1.4 フォレストの読み取り
フォレストはテスターパスの最後にテスターで読み取られるため、これに対応するようにウィザードアセンブルEAを変更します。
//+------------------------------------------------------------------+ //| "Tester" event handler function | //+------------------------------------------------------------------+ double OnTester() { signal_se.ReadForest(); return(0.0); }
2.2 EAマネークラス
この記事では、ウィザードで使用するカスタマイズされた位置サイジングクラスの作成についても検討しています。「MoneySizeOptimized」クラスを使用して、シャノンエントロピーに基づいてポジションサイズを正規化するように修正します。新しいインターフェイスは次のようになります。
// wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Trading with 'Shannon Entropy' optimized trade volume | //| Type=Money | //| Name=SE | //| Class=CMoneySE | //| Page=money_se | //| Parameter=ScaleFactor,int,3,Scale factor | //| Parameter=Percent,double,10.0,Percent | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| Class CMoneySE. | //| Purpose: Class of money management with 'Shannon Entropy' optimized volume. | //| Derives from class CExpertMoney. | //+------------------------------------------------------------------+ class CMoneySE : public CExpertMoney { protected: int m_scale_factor; public: double m_absolute_condition; CMoneySE(void); ~CMoneySE(void); //--- void ScaleFactor(int scale_factor) { m_scale_factor=scale_factor; } void AbsoluteCondition(double absolute_condition) { m_absolute_condition=absolute_condition; } virtual bool ValidationSettings(void); //--- virtual double CheckOpenLong(double price,double sl); virtual double CheckOpenShort(double price,double sl); protected: double Optimize(double lots); };
変数「m_absolute_condition」は、「LongCondition」および「ShortCondition」関数によって返される整数の絶対値になります。これは正規化された値であるため、そのサイズを使用してポジションサイズを比例させることができます。この変数は、ウィザードアセンブルEAへの変更を介して、シグナルクラスからマネークラスに渡されます。
//+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CSignalSE *signal_se; CMoneySE *money_se;
そしてティック関数について
//+------------------------------------------------------------------+ //| "Tick" event handler function | //+------------------------------------------------------------------+ void OnTick() { if(!signal_se.m_read_forest) signal_se.WriteForest(); money_se.AbsoluteCondition(fabs(signal_se.m_last_condition)); ExtExpert.OnTick(); }
主な変更点は、以下のOptimize関数にあります。
//+------------------------------------------------------------------+ //| Optimizing lot size for open. | //+------------------------------------------------------------------+ double CMoneySE::Optimize(double lots) { double lot=lots; //--- normalize lot size based on magnitude of condition lot*=(20*m_scale_factor/fmax(20.0,((100.0-m_absolute_condition)/100.0)*20.0*m_scale_factor*m_scale_factor)); //--- reduce lot based on number of losses orders without a break if(m_scale_factor>0) { //--- select history for access HistorySelect(0,TimeCurrent()); //--- int orders=HistoryDealsTotal(); // total history deals int losses=0; // number of consequent losing orders CDealInfo deal; //--- for(int i=orders-1;i>=0;i--) { deal.Ticket(HistoryDealGetTicket(i)); if(deal.Ticket()==0) { Print("CMoneySE::Optimize: HistoryDealGetTicket failed, no trade history"); break; } //--- check symbol if(deal.Symbol()!=m_symbol.Name()) continue; //--- check profit double profit=deal.Profit(); if(profit>0.0) break; if(profit<0.0) losses++; } //--- if(losses>1){ lot*=m_scale_factor; lot/=(losses+m_scale_factor); lot=NormalizeDouble(lot,2);} } //--- normalize and check limits double stepvol=m_symbol.LotsStep(); lot=stepvol*NormalizeDouble(lot/stepvol,0); //--- double minvol=m_symbol.LotsMin(); if(lot<minvol){ lot=minvol; } //--- double maxvol=m_symbol.LotsMax(); if(lot>maxvol){ lot=maxvol; } //--- return(lot); }
3.0 MQL5ウィザード
2つのEAを組み立てます。1つは、作成したシグナルクラスとマネー管理用の最小取引高取引のみを備え、もう1つは、作成したシグナルとマネー管理クラスの両方を備えています。
4.0 ストラレジーテスター
最初のEAの最適化を実行すると、2.89のプロフィットファクターと4.87のシャープレシオが得られますが、2番目のEAの最適化では3.65のプロフィットファクターと5.79のシャープレシオが得られます。
最初のレポート
最初のエクイティカーブ
二次報告
2番目のエクイティカーブ
5.0 結論
付属のEAは、理想的なテイクプロフィットとストップロスレベルの4時間枠の始値で最適化されています。つまり、これらの結果をライブ口座やすべてのティックモードのストラテジーテスターで再現することはできません。それは記事のポイントではありませんでした。再現すべき聖杯を見つけようとするのではなく、この連載ではすべてのトレーダーが自分の優位性を思いつくことができるように、さらにカスタマイズできる明確なアイデアを明らかにしようとします。市場は依然として全体的に相関が強すぎるため、ボラティリティが高い場合にエッジを探すことがうまく機能します。ご精読ありがとうございました。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/11487
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索