MQL5 ウィザード:計算された価格での発注、ストップロスおよびテイクプロフィット設定標準ライブラリ拡張
はじめに
MQL5 標準ライブラリは厳格なアーキテクチャを要求する大型プロジェクト開発には有用な助っ人です。MQL5 ウィザードにより既製のパーツを数分でダイアログモードの大規模なスキームに組み込むことができます。それは過大評価ではありません。MQL5 ウィザードはエキスパートの全パーツの収集を自動化し、ハンドラに従いエキスパート内でモジュールパラメ―だを自動で宣言します。多数の多様なモジュールがある場合、そのような自動化により時間とルーチン処理が大幅に節約されます。
すばらしいように聞こえますが、そこには明らかな欠点があります。標準クラスを基にウィザードで作成されるトレーディング済む手無の能力には限界があるのです。本稿では作成されたエキスパートの機能性を大きく拡張することのできる汎用的な方法を考察します。この方法が取り入れられれば、ウェザードと標準モジュールの互換性は維持されます。
その方法の考え方はオブジェクト指向プログラミングの継承と多型のメカニズムを利用、または一般化されたエキスパートのコード内で標準クラスに代替するためにクラスを作成するというものです。このようにウィザードと標準ライブラリの利点がすべて利用されます。それが必要な機能を備えるエキスパートの作成につながるのです。そかしそこに至るには、コードがわずかに減らされる必要があります。4文字列分です。
本稿の実用目的は、作成済みエキスパートに現行価格からの指定距離ばかりでなく希望の価格レベルで発注、ストップロスおよびテイクプロフィットを設定する機能を追加することです。
記事『MQL5 ウィザード:任意の価格で指値注文をオープンすることを EA に教える方法』で同様の考え方が述べられています。提案のソリューションにある重大な欠点は、下位フィルターからのトレードシグナルモジュールのパラメータが『強制的に』変更されることです。この方法は多数のモジュールと連携することを勧めませんが、処理最適化にウィザードを使用することには意味がありません。
標準クラスから継承されるクラスにおいて任意の価格でストップロスおよびテイクプロフィット設定だけでなく発注を実装することを以下で詳しく考察します。それはモジュール間の競合はあり得ないということです。本稿が例としての役割を果たし、読者のみなさんがご自身の標準的フレームワークを改良し、またユーザーの方々がライブラリの拡張を実装するきっかけになることを願っております。
1. 意思決定の標準的アルゴリズム
MQL5 ウィザードで一般化されたエキスパートは CExpert クラスインスタンスに基づいています。CExpertSignal クラスのオブジェクトに対するポインターはこのクラスで宣言されます。本稿では以降、簡潔化のためこのオブジェクトはメインシグナルと呼ばれます。メインシグナルは下位フィルターに対するポインターを持ちます(シグナルモジュールはCExpertSignal クラスの継承者です)。
オープンしたポジションも注文もない場合、エキスパートはメインシグナルを参照して新規ティックでポジションをオープンするチャンスを見ます。メインシグナルは下位フィルターひとつずつに問い合わせ、取得された予測に基づく加重平均推定(方向)を計算します。その値が閾値(メインシグナルの m_threshold_open パラメータの値)を超える場合、オーダーパラメータとbool タイプの条件についての確認結果がエキスパートに渡されます。これら条件が合えば、マーケット価格でポジションがオープンされるか、マーケット価格から一定距離の未決注文が出されます(図1参照)。ストップロスは固定距離でのみ設定可能です。エキスパート設定にて、始値のインデント、マーケット価格からのストップロスとテイクプロフィットが指定され、メインシグナルの変数 m_price_level、m_stop_level、 m_take_levelにそれぞれ格納されます。
よって、現在発注のためには条件が2件満たされる必要があります。
- 現シンボルについてはオープンポジションはありません。
- 加重平均推定の絶対値は閾値を越えています。これはトレントがかなり強いことを意味します。
図1 マーケットエンター時の意思決定パターン
図1の現在の意思決定パターンは MQL5 ウィザードの適用領域を大幅に制限します。ストップロスの固定値での戦略は変化しやすい変動率により長期トレードには不適切です。未決注文を採用するシステムでは通常それらを動的計算レベルで出す必要があります。
2. 意思決定の標準的アルゴリズム
計算レベルと発注の観点から、これは言い詰まり状態です。というのも、シグナルモジュールが予測値以外は作成できず、メインシグナルはレベルと連動するように設計されていないからです。このため以下が提案されます。
- オーダーパラメータを作成できる新しいタイプのシグナルモジュール(それを価格モジュールと呼ぶことにします)を導入する。
- メインシグナルがこういったパラメータを処理できるようにトレーニングします。すなわち最良のパラメータを選択しそれをエキスパートに渡すようにするのです。
修正されたアルゴリズム(図2)により未決注文以外のその他リクエストを処理することが可能となります。このアルゴリズムの基本はエントリーポイント(価格)をトレンド決定(加重平均予測)から分離することです。それは判断のためにはフィルターによる好ましいトレード方向が決定され、価格モジュールから取得される適切な方向をもつオーダーパラメータが選択されるということです。類似した利用可能なセットが複数ある場合、最大荷重(パラメータ値 m_weight が選ばれます)を伴ってモジュールから受信されるセットです。報告が決定されているが現在利用可能なエントリーポイントがない場合、エキスパートはアクティブではありません。
図2 マーケットエンター時の意思決定パターン
新規発注には以下の条件が満たされる必要があります。
- 現シンボルにつしてオープンしているポジションおよび注文がない。
- 加重平均推定の絶対値は閾値を越えている。
- 最低オープンしている注文価格が一つはある。
図2のアルゴリズムは、方向でフィルターをかけ、エキスパートで最良のものを選び、数多くの可能なエントリーポイントを処理することができます。
3. エキスパートとシグナルモジュールの修正クラス作成
ライブラリの拡張は2つのクラスを基にします。それぞれCExpertSignal とCExpert から継承されるCExpertSignalAdvanced と CExpertAdvancedです。
新機能を追加する方法はすべてエキスパートの異なるブロック間でデータ交換をするためのインターフェース変更を目的としています。たとえば、図2パターンのアルゴリズム実装にはメインシグナル(CExpertSignalAdvancedクラス)と価格モジュール(そのクラスの継承クラス)の連携が必要です。データ変更の際、すでに出された注文を更新することはエキスパート(CExpertAdvancedクラス)とメインシグナルの連携を意味します。
この段階では、図2パターンを実装し、パラメータ変更時(たとえばより有利なエントリーポイントの出現時)のポジションオープンとすでに出されている注文の更新を行います。CExpertSignalAdvanced クラスを考察します。
3.1. CExpertSignalAdvanced
このクラスはメインシグナルの役目をするその上位クラスに代わり、その上位クラスがシグナルモジュールに対して基本クラスであるのと同様に価格モジュールに対する基本クラスになろうとします。
class CExpertSignalAdvanced : public CExpertSignal { protected: //---data members for storing parameters of the orders being placed double m_order_open_long; //opening price of the order to buy double m_order_stop_long; //Stop Loss of the order to buy double m_order_take_long; //Take Profit of the order to buy datetime m_order_expiration_long; //expiry time of the order to buy double m_order_open_short; //opening price of the order to sell double m_order_stop_short; //Stop Loss of the order to sell double m_order_take_short; //Take Profit of the order to sell datetime m_order_expiration_short; //expiry time of the order to sell //--- int m_price_module; //index of the first price module in the m_filters array public: CExpertSignalAdvanced(); ~CExpertSignalAdvanced(); virtual void CalcPriceModuleIndex() {m_price_module=m_filters.Total();} virtual bool CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration); virtual bool CheckOpenShort(double &price,double &sl,double &tp,datetime &expiration); virtual double Direction(void); //calculating weighted average forecast based on the data received from signal modules virtual double Prices(void); //updating of parameters of the orders being placed according to the data received from price modules virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex); virtual bool CheckUpdateOrderShort(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex); double getOpenLong() { return m_order_open_long; } double getOpenShort() { return m_order_open_short; } double getStopLong() { return m_order_stop_long; } double getStopShort() { return m_order_stop_short; } double getTakeLong() { return m_order_take_long; } double getTakeShort() { return m_order_take_short; } datetime getExpLong() { return m_order_expiration_long; } datetime getExpShort() { return m_order_expiration_short; } double getWeight() { return m_weight; } };
オーダーパラメータを格納するためのデータメンバーは CExpertSignalAdvancedクラスで宣言されます。これら変数の値は Prices() メソッドで更新されます。これら変数はバッファの役目をします。
それからパラメータm_price_module が宣言されます。それは最初の価格モジュールのインデックスをCExpertSignalで宣言される m_filters 配列に格納します。この配列はインクルードされたシグナルモジュールに対するポインターを持ちます。標準モジュール(フィルター)に対するポインターは配列の最初に格納されます。その後、m_price_module インデックスかた始まり価格モジュールが来ます。インディケータと時系列の初期化メソッドを変更しなくてすむように、すべてを配列に格納する判断がされました。また、1配列に64モジュールをインクルードする可能性があり、それは通常十分です。
その上、CExpertSignalAdvanced クラスでヘルパーメソッドが 宣言され、保護されたデータメンバーの値を取得します。その名前は get(クラス宣言参照)で始まります。
3.1.1. コンストラクタ
コンストラクタ CExpertSignalAdvanced はクラス内部で宣言された変数を初期化します。
CExpertSignalAdvanced::CExpertSignalAdvanced() { m_order_open_long=EMPTY_VALUE; m_order_stop_long=EMPTY_VALUE; m_order_take_long=EMPTY_VALUE; m_order_expiration_long=0; m_order_open_short=EMPTY_VALUE; m_order_stop_short=EMPTY_VALUE; m_order_take_short=EMPTY_VALUE; m_order_expiration_short=0; m_price_module=-1; }
3.1.2. CalcPriceModuleIndex()
CalcPriceModuleIndex() メソッドは配列エレメントの次の追加モジュールのインデックスに等しい現在ナンバー m_price_module に割り当てられます。このメソッドは最初の価格モジュールの追加前に呼ばれます。関数本体はクラス宣言にあります。
virtual void CalcPriceModuleIndex() {m_price_module=m_filters.Total();}
3.1.3. CheckOpenLong(...) and CheckOpenShort(...)
CheckOpenLong(...) メソッドは CExpertクラスインスタンスから呼ばれ、以下の説明のように動作します。
- インクルードされた価格モジュールを確認します。なにもなければ、親クラスのエポニムメソッドを呼びます。
- Direction() method;から加重平均予測値(方向)を受け取ります。
- EMPTY_VALUE とm_threshold_openの閾値と比較することでエントリー条件が合えば検証します。
- Prices() メソッドによりオーダーパラメータを更新し、それを OpenLongParams(...) 関数でエキスパートに渡します。この関数の結果を保存します。
- 保存した結果を返します。
bool CExpertSignalAdvanced::CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration) { //--- if price modules were not found, call the method of the basic class CExpertSignal if(m_price_module<0) return(CExpertSignal::CheckOpenLong(price,sl,tp,expiration)); bool result =false; double direction=Direction(); //--- prohibitive signal if(direction==EMPTY_VALUE) return(false); //--- check for exceeding the threshold if(direction>=m_threshold_open) { Prices(); result=OpenLongParams(price,sl,tp,expiration);//there's a signal if m_order_open_long!=EMPTY_VALUE } //--- return the result return(result); }
CheckOpenShort(...) は同じ処理原則を持ち、同じように使用されます。よってこれについては考察しません。
3.1.4 Direction()
Direction() メソッドはフィルターと加重平均予測値の計算が必要とします。このメソッドは親クラス CExpertSignal のエポニムメソッドと非常に似ていて、例外はループで m_filters 配列のエレメントをすべて参照するのではなく、0 からm_price_moduleより1小さい変化をするインデックスを持つエレメントのみ参照することです。それ以外はCExpertSignal::Direction()に類似しています。
double CExpertSignalAdvanced::Direction(void) { long mask; double direction; double result=m_weight*(LongCondition()-ShortCondition()); int number=(result==0.0)? 0 : 1; // number of queried modules //--- loop by filters for(int i=0;i<m_price_module;i++) { //--- mask for bitmaps (variables, containing flags) mask=((long)1)<<i; //--- checking for a flag of ignoring a filter signal if((m_ignore&mask)!=0) continue; CExpertSignal *filter=m_filters.At(i); //--- checking for a pointer if(filter==NULL) continue; direction=filter.Direction(); //--- prohibitive signal if(direction==EMPTY_VALUE) return(EMPTY_VALUE); if((m_invert&mask)!=0) result-=direction; else result+=direction; number++; } //--- averaging the sum of weighted forecasts if(number!=0) result/=number; //--- return the result return(result); }
3.1.5. Prices()
メソッド Prices() はm_price_module インデックスから始め最後まで m_filters 配列の2番目のパートを反復します。これは価格モジュールを問合せ、関数 OpenLongParams(...) と OpenShortParams(...) によってクラス変数値を更新します。サイクル前にパラメータ値が消去されます。
現価格モジュール(m_weight)の加重が、値を提供した前回問合せを受けたモジュールの加重より大きければ、サイクル中、パラメータ値は上書きされます。結果、空のパラメータが残される(なにも見つからなければ)か、メソッド呼び出し時に最良の加重平均を持つパラメータとなります。
double CExpertSignalAdvanced::Prices(void) { m_order_open_long=EMPTY_VALUE; m_order_stop_long=EMPTY_VALUE; m_order_take_long=EMPTY_VALUE; m_order_expiration_long=0; m_order_open_short=EMPTY_VALUE; m_order_stop_short=EMPTY_VALUE; m_order_take_short=EMPTY_VALUE; m_order_expiration_short=0; int total=m_filters.Total(); double last_weight_long=0; double last_weight_short=0; //--- cycle for price modules for(int i=m_price_module;i<total;i++) { CExpertSignalAdvanced *prm=m_filters.At(i); if(prm==NULL) continue; //--- ignore the current module if it has returned EMPTY_VALUE if(prm.Prices()==EMPTY_VALUE)continue; double weight=prm.getWeight(); if(weight==0.0)continue; //--- select non-empty values from modules with the greatest weight if(weight>last_weight_long && prm.getExpLong()>TimeCurrent()) if(prm.OpenLongParams(m_order_open_long,m_order_stop_long,m_order_take_long,m_order_expiration_long)) last_weight_long=weight; if(weight>last_weight_short && prm.getExpShort()>TimeCurrent()) if(prm.OpenShortParams(m_order_open_short,m_order_stop_short,m_order_take_short,m_order_expiration_short)) last_weight_short=weight; } return(0); }
3.1.6. OpenLongParams(...) と OpenShortParams(...)
CExpertSignalAdvanced クラス内では、メソッドOpenLongParams(...) が入力パラメータに対するクラス変数からの参照による買い注文パラメータ値を渡します。
親クラス内でのこのメソッドの役割はやや異なります。これはマーケット価格を基に必要なパラメータとメインシグナルで指定されるくぼみを計算していました。ここではこれは準備済みのパラメータを渡すだけです。始値が正しければ(EMPTY_VALUEに等しくない)、メソッドは真を、それ以外は偽を返します。
bool CExpertSignalAdvanced::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { if(m_order_open_long!=EMPTY_VALUE) { price=m_order_open_long; sl=m_order_stop_long; tp=m_order_take_long; expiration=m_order_expiration_long; return(true); } return(false); }
OpenShortParams(...) については考察しません。この処理原則は同じで使用方法も類似しているためです。
3.1.7. CheckUpdateOrderLong(...) と CheckUpdateOrderShort(...)
メソッドCheckUpdateOrderLong(...)と CheckUpdateOrderShort(...) は CExpertAdvanced クラスと呼ばれます。それらはすでに出された未決注文を最終の価格レベルに従って更新するのに使用されます。
メソッド CheckUpdateOrderLong(...) をもっとよく検討していきます。最初の価格レベルでメソッド Prices(...),を呼ぶとき更新され、それから起、します。最後に、更新済みデータを渡し結果を返すためにメソッド OpenLongParams(...) が呼ばれます。
bool CExpertSignalAdvanced::CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex) { Prices(); //update prices //--- check for changes double point=m_symbol.Point(); if( MathAbs(order_ptr.PriceOpen() - m_order_open_long)>point || MathAbs(order_ptr.StopLoss() - m_order_stop_long)>point || MathAbs(order_ptr.TakeProfit()- m_order_take_long)>point || order_ptr.TimeExpiration()!=m_order_expiration_long) return(OpenLongParams(open,sl,tp,ex)); //--- update is not required return (false); }
CheckUpdateOrderShort(...) については考察しません。これは同様の処理を行い同じ方法で適用されるためです。
3.2. CExpertAdvanced
エキスパートのクラスにおける変更はメインシグナル上、こうサインされたデータに従いすでに出された発注を変更することだけに関連します。以下は CExpertAdvanced クラス宣言の表記です。
class CExpertAdvanced : public CExpert { protected: virtual bool CheckTrailingOrderLong(); virtual bool CheckTrailingOrderShort(); virtual bool UpdateOrder(double price,double sl,double tp,datetime ex); public: CExpertAdvanced(); ~CExpertAdvanced(); };
ご覧のとおり、メソッドはほとんどなくコンストラクタとデストラクタは空です。
3.2.1. CheckTrailingOrderLong() and CheckTrailingOrderShort()
メソッド CheckTrailingOrderLong()は基本クラスのエポニムメソッドをオーバーライドし、注文を変更する必要があるか見極めるため、メインシグナルのメソッド CheckUpdateOrderLong(...) を呼び出します。変更が必要であれば、メソッド UpdateOrder(...) が呼ばれ結果が返されます。
bool CExpertAdvanced::CheckTrailingOrderLong(void) { CExpertSignalAdvanced *signal_ptr=m_signal; //--- check for the opportunity to modify the order to buy double price,sl,tp; datetime ex; if(signal_ptr.CheckUpdateOrderLong(GetPointer(m_order),price,sl,tp,ex)) return(UpdateOrder(price,sl,tp,ex)); //--- return with no actions taken return(false); }
メソッドCheckTrailingOrderShort()はこれと似ていて同じ用法です。
3.2.2. UpdateOrder()
UpdateOrder() 関数は関連する価格(EMPTY_VALUEではない)のチェックを始めます。なにもなければ、注文は削除され、そうでなければ受信したパラメータに従って注文は変更されます。
bool CExpertAdvanced::UpdateOrder(double price,double sl,double tp,datetime ex) { ulong ticket=m_order.Ticket(); if(price==EMPTY_VALUE) return(m_trade.OrderDelete(ticket)); //--- modify the order, return the result return(m_trade.OrderModify(ticket,price,sl,tp,m_order.TypeTime(),ex)); }
標準クラスの継承クラス作成は複雑です。
4. 価格モジュールの作成
すでに発注、計算されたレベルでのストップロス設定を行うエキスパート作成の基本は取得しました。正確には、価格レベルを処理するためにすぐに使用できるクラスを取得しました。そのレベルを生成するモジュールを書くことが残されているのみです。
価格モジュールの作成手順はトレードシグナルのモジュールを書くのと似ています。唯一の相違は Prices()で、これがモジュール内での価格更新を行うメソッドで、オーバーライドされる必要があることです。シグナルモジュールのように LongCondition()、 ShortCondition() やDirection() ではありません。理想的には読者のみなさんはシグナルモジュール開発について明確な考えを持つ必要があります。この件については、記事 "Create Your Own Trading Robot in 6 Steps!" および "Trading Signal Generator Based on a Custom Indicator" が役に立つのではないでしょうか。
価格モジュールのコードがいくつか例として役立つことと思います。
4.1. "Delta ZigZag" インディケータに基づく価格モジュール
インディケータDelta ZigZag は複数の直近のピークの指定数によってレベルを描きます。価格がこれらレベルをクロスすると、それはトレンド反転の可能性を意味します。
価格モジュールの目的はインディケータバッファからエントリーレベルを取り込み、ストップロスを設定するために一番近い極値を見つけ、設定で指定された係数でストップロスを乗算することでテイクプロフィットを計算することです。
図3 例として買い注文を使用したデルタ ZigZag インディケータでの価格モジュール価格モジュール処理の図
図3 価格モジュールによって生成されたレベルを示しています。オーダーが実行される前に、最小値の更新に従い適切にストップロスおよびテイクプロフィットが変化します。
4.1.1. モジュール記述
// wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=DeltaZZ Price Module | //| Type=SignalAdvanced | //| Name=DeltaZZ Price Module | //| ShortName=DeltaZZ_PM | //| Class=CPriceDeltaZZ | //| Page=not used | //| Parameter=setAppPrice,int,1, Applied price: 0 - Close, 1 - H/L | //| Parameter=setRevMode,int,0, Reversal mode: 0 - Pips, 1 - Percent | //| Parameter=setPips,int,300,Reverse in pips | //| Parameter=setPercent,double,0.5,Reverse in percent | //| Parameter=setLevels,int,2,Peaks number | //| Parameter=setTpRatio,double,1.6,TP:SL ratio | //| Parameter=setExpBars,int,10,Expiration after bars number | //+------------------------------------------------------------------+ // wizard description end
記述内の最初の5個のパラメータは使用するインディケータの設定に必要です。それからストップロスに基づくテイクプロフィットの計算用係数と現価格モジュールからの注文に対するバーでの期限がきます。
4.1.2. クラス宣言
class CPriceDeltaZZ : public CExpertSignalAdvanced { protected: CiCustom m_deltazz; //object of the DeltaZZ indicator //--- module settings int m_app_price; int m_rev_mode; int m_pips; double m_percent; int m_levels; double m_tp_ratio; //tp:sl ratio int m_exp_bars; //lifetime of the orders in bars //--- method of indicator initialization bool InitDeltaZZ(CIndicators *indicators); //--- helper methods datetime calcExpiration() { return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); } double getBuySL(); //function for searching latest minimum of ZZ for buy SL double getSellSL(); //function for searching latest maximum of ZZ for sell SL public: CPriceDeltaZZ(); ~CPriceDeltaZZ(); //--- methods of changing module settings void setAppPrice(int ap) { m_app_price=ap; } void setRevMode(int rm) { m_rev_mode=rm; } void setPips(int pips) { m_pips=pips; } void setPercent(double perc) { m_percent=perc; } void setLevels(int rnum) { m_levels=rnum; } void setTpRatio(double tpr) { m_tp_ratio=tpr; } void setExpBars(int bars) { m_exp_bars=bars;} //--- method of checking correctness of settings virtual bool ValidationSettings(void); //--- method of creating indicators virtual bool InitIndicators(CIndicators *indicators); //--- main method of price module updating the output data virtual double Prices(); };
保護データ-CiCustomタイプのインディケータのオブジェクトと記述で指定されたタイプに従ったパラメータがモジュール内で宣言されます。
4.1.3. コンストラクタ
コンストラクタはデフォルト値でクラスパラメータを初期化します。そののち、モジュールがエキスパートの入力パラメータによるメインシグナルにインクルードされるとき、これら値は再び初期化されます。
CPriceDeltaZZ::CPriceDeltaZZ() : m_app_price(1), m_rev_mode(0), m_pips(300), m_percent(0.5), m_levels(2), m_tp_ratio(1), m_exp_bars(10) { }
4.1.4. ValidationSettings()
ValidationSettings() は入力パラメータを確認するために重要なメソッドです。モジュールパラメータの値が無効であれば、結果偽が返され、エラーメッセージがジャーナルに表示されます。
bool CPriceDeltaZZ::ValidationSettings(void) { //--- checking for settings of additional filters if(!CExpertSignal::ValidationSettings()) return(false); //--- initial data check if(m_app_price<0 || m_app_price>1) { printf(__FUNCTION__+": Applied price must be 0 or 1"); return(false); } if(m_rev_mode<0 || m_rev_mode>1) { printf(__FUNCTION__+": Reversal mode must be 0 or 1"); return(false); } if(m_pips<10) { printf(__FUNCTION__+": Number of pips in a ray must be at least 10"); return(false); } if(m_percent<=0) { printf(__FUNCTION__+": Percent must be greater than 0"); return(false); } if(m_levels<1) { printf(__FUNCTION__+": Ray Number must be at least 1"); return(false); } if(m_tp_ratio<=0) { printf(__FUNCTION__+": TP Ratio must be greater than zero"); return(false); } if(m_exp_bars<0) { printf(__FUNCTION__+": Expiration must be zero or positive value"); return(false); } //--- parameter check passed return(true); }
4.1.5. InitIndicators(...)
InitIndicators(...) メソッドは基本クラスのエポニムメソッドを呼び、InitDeltaZZ(...) メソッドによって現モジュールのインディケータを初期化します。
bool CPriceDeltaZZ::InitIndicators(CIndicators *indicators) { //--- initialization of indicator filters if(!CExpertSignal::InitIndicators(indicators)) return(false); //--- creating and initializing of custom indicator if(!InitDeltaZZ(indicators)) return(false); //--- success return(true); }
4.1.6. InitDeltaZZ(...)
InitDeltaZZ(...) メソッドはカスタムインディケータのオブジェクトをコレクションに追加し、新規インディケータ『デルタ ZigZag』を作成します。
bool CPriceDeltaZZ::InitDeltaZZ(CIndicators *indicators) { //--- adds to collection if(!indicators.Add(GetPointer(m_deltazz))) { printf(__FUNCTION__+": error adding object"); return(false); } //--- specifies indicator parameters MqlParam parameters[6]; //--- parameters[0].type=TYPE_STRING; parameters[0].string_value="deltazigzag.ex5"; parameters[1].type=TYPE_INT; parameters[1].integer_value=m_app_price; parameters[2].type=TYPE_INT; parameters[2].integer_value=m_rev_mode; parameters[3].type=TYPE_INT; parameters[3].integer_value=m_pips; parameters[4].type=TYPE_DOUBLE; parameters[4].double_value=m_percent; parameters[5].type=TYPE_INT; parameters[5].integer_value=m_levels; //--- object initialization if(!m_deltazz.Create(m_symbol.Name(),m_period,IND_CUSTOM,6,parameters)) { printf(__FUNCTION__+": error initializing object"); return(false); } //--- number of the indicator buffers if(!m_deltazz.NumBuffers(5)) return(false); //--- ок return(true); }
メソッドValidationSettings()、 InitDeltaZZ(...)、InitIndicators(...) が正常に完了したら、モジュールは初期化され処理に備えます。
4.1.7. Prices()
このメソッドは価格モジュールの基本です。これがまさにオーダーパラメータが更新される場所です。その値はメインシグナルに渡されます。このメソッドは double タイプで処理結果を返します。これは主に将来の開発のために実装されました。Prices() メソッドの結果はいくつか特殊な状況やイベントをコード化し、メインシグナルがそれらを適切に処理できるようにします。現在は戻り値 EMPTY_VALUE を処理するのみです。この結果受け取りの際、メインシグナルはモジュールによって提案されるパラメータを無視します。
このモジュールにおける Prices() メソッドの処理アルゴリズムは以下です。
- インディケータバッファ3および4よりそれぞれ買いと売りに対し注文の始値を取り込みます。
- 注文の最終パラメータはゼロ設定します。
- 買い価格を確認します。一つあれば、getBuySL() メソッドによりストップロスを設定するレベルを特使し、ストップロス値と始値からテイクプロフィットを計算し、注文期限を計算します。
- 売り価格を確認します。検出されれば、getSellSL() メソッドによりストップロスを設定するレベルを特使し、ストップロス値と始値からテイクプロフィットを計算し、注文期限を計算します。
- 終了する。
double CPriceDeltaZZ::Prices(void) { double openbuy =m_deltazz.GetData(3,0);//receive the last value from buffer 3 double opensell=m_deltazz.GetData(4,0);//receive the last value from buffer 4 //--- clear parameter values m_order_open_long=EMPTY_VALUE; m_order_stop_long=EMPTY_VALUE; m_order_take_long=EMPTY_VALUE; m_order_expiration_long=0; m_order_open_short=EMPTY_VALUE; m_order_stop_short=EMPTY_VALUE; m_order_take_short=EMPTY_VALUE; m_order_expiration_short=0; int digits=m_symbol.Digits(); //--- check for the prices to buy if(openbuy>0)//if buffer 3 is not empty { m_order_open_long=NormalizeDouble(openbuy,digits); m_order_stop_long=NormalizeDouble(getBuySL(),digits); m_order_take_long=NormalizeDouble(m_order_open_long + m_tp_ratio*(m_order_open_long - m_order_stop_long),digits); m_order_expiration_long=calcExpiration(); } //--- check for the prices to sell if(opensell>0)//if buffer 4 is not empty { m_order_open_short=NormalizeDouble(opensell,digits); m_order_stop_short=NormalizeDouble(getSellSL(),digits); m_order_take_short=NormalizeDouble(m_order_open_short - m_tp_ratio*(m_order_stop_short - m_order_open_short),digits); m_order_expiration_short=calcExpiration(); } return(0); }
4.1.8. getBuySL() and getSellSL()
メソッドgetBuySL() と getSellSL() はストップロスのためにそれぞれ極小値と極大値を検索しています。関連バッファ内の最後のゼロ以外の値- 最後の極値の価格レベルは各メソッドで検索されます。
double CPriceDeltaZZ::getBuySL(void) { int i=0; double sl=0.0; while(sl==0.0) { sl=m_deltazz.GetData(0,i); i++; } return(sl); } double CPriceDeltaZZ::getSellSL(void) { int i=0; double sl=0.0; while(sl==0.0) { sl=m_deltazz.GetData(1,i); i++; } return(sl); }
4.2. インサイドバーに基づく価格モジュール
インサイドバーはトレードで幅広く利用されるモデルまたはパターンでプライスアクションと呼ばれるインディケータは持ちません。インサイドバーは前回バーの極値ないで最高値、最低値を持つバーです。インサイドバーは価格変動の統合または可能性ある反転の始まりを示します。
図4ではインサイドバーは赤の楕円内にあります。インサイドバーが検出される場合、価格モジュールはインサイドバーに先行するバーの極値に買いストップと売りストップの始値を生成します。
始値は逆注文に対してはストップロスレベルです。テイクプロフィットは前述のモジュールと同じ方法で計算されます。-設定で指定された係数でストップロスを乗算する、です。始値とストップロスは赤の横線で、テイクプロフィットはグリーンの線でマークされています。
図4 インサイドバーと価格モジュールレベルの図
図4ではトレンドが上向けのため売り注文は出されていません。それでもなお、価格モジュールは両方向にエントリーレベルを生成します。テイクプロフィットレベルの中にはプロットの外側にあるものもあります。
前のモジュールと異なり、このモジュールのアルゴリズムはシンプルでインディケータを初期化する必要はありません。モジュール内にはほとんどコードはありません。以下でそれを完全に示しています。関数をそれぞれ個別に分析することはしません。
#include <Expert\ExpertSignalAdvanced.mqh> // wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Inside Bar Price Module | //| Type=SignalAdvanced | //| Name=Inside Bar Price Module | //| ShortName=IB_PM | //| Class=CPriceInsideBar | //| Page=not used | //| Parameter=setTpRatio,double,2,TP:SL ratio | //| Parameter=setExpBars,int,10,Expiration after bars number | //| Parameter=setOrderOffset,int,5,Offset for open and stop loss | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| Class CPriceInsideBar | //| Purpose: Class of the generator of price levels for orders based | //| on the "inside bar" pattern. | //| Is derived from the CExpertSignalAdvanced class. | //+------------------------------------------------------------------+ class CPriceInsideBar : public CExpertSignalAdvanced { protected: double m_tp_ratio; //tp:sl ratio int m_exp_bars; //lifetime of the orders in bars double m_order_offset; //shift of the opening and Stop Loss levels datetime calcExpiration() { return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); } public: CPriceInsideBar(); ~CPriceInsideBar(); void setTpRatio(double ratio){ m_tp_ratio=ratio; } void setExpBars(int bars) { m_exp_bars=bars;} void setOrderOffset(int pips){ m_order_offset=m_symbol.Point()*pips;} bool ValidationSettings(); double Prices(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CPriceInsideBar::CPriceInsideBar() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CPriceInsideBar::~CPriceInsideBar() { } //+------------------------------------------------------------------+ //| Validation of protected settings | //+------------------------------------------------------------------+ bool CPriceInsideBar::ValidationSettings(void) { //--- verification of the filter parameters if(!CExpertSignal::ValidationSettings()) return(false); //--- initial data check if(m_tp_ratio<=0) { printf(__FUNCTION__+": TP Ratio must be greater than zero"); return(false); } if(m_exp_bars<0) { printf(__FUNCTION__+": Expiration must be zero or positive value"); return(false); } //--- check passed return(true); } //+------------------------------------------------------------------+ //| Price levels refreshing | //+------------------------------------------------------------------+ double CPriceInsideBar::Prices(void) { double h[2],l[2]; if(CopyHigh(m_symbol.Name(),m_period,1,2,h)!=2 || CopyLow(m_symbol.Name(),m_period,1,2,l)!=2) return(EMPTY_VALUE); //--- check for inside bar if(h[0] >= h[1] && l[0] <= l[1]) { m_order_open_long=h[0]+m_order_offset; m_order_stop_long=l[0]-m_order_offset; m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio; m_order_expiration_long=calcExpiration(); m_order_open_short=m_order_stop_long; m_order_stop_short=m_order_open_long; m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio; m_order_expiration_short=m_order_expiration_long; return(0); } return(EMPTY_VALUE); } //+------------------------------------------------------------------+
このモジュールのメソッド Prices() では、適用可能な価格レベルがないことを示すために結果の戻り値が使用されます。
4.3. アウトサイドバーに基づく価格モジュール
アウトサイドバーはプライスアクションの人気のあるもうひとつ別のパターンで、『アブソープション』とも呼ばれます。アウトサイドバーがそのように呼ばれるのは、その高値と安値が前回バーの高値と安値に重複するためです。アウトサイドバーの見かけは指示された価格変動によるボラティリティの増加のインディケータのようです。
図5ではアウトサイドバーは赤い楕円でマークされています。アウトサイドバーが検出される場合、価格モジュールはその極値に買いストップと売りストップの始値を生成します。
始値は逆注文に対してはストップロスレベルです。テイクプロフィットは前述のモジュールと同じ方法で計算されます。-設定で指定された係数でストップロスを乗算する、です。始値とストップロスは赤の横線で、テイクプロフィットはグリーンの線でマークされています。
図5 アウトサイドバーと価格モジュールレベルの図
図5ではアウトサイドバーは赤い楕円でマークされています。横線は価格モジュールによって生成された未決注文の始値を反映しています。
この場合、下降トレンドがあるため売り注文が1件だけオープンされています。このモジュールの処理は基本的に前のモジュールに類似しています。唯一の違いはそのコードが Prices()メソッドだけで、その他の部分はまったく同じで名前も同じです。以下はPrices()のコードです。
double CPriceOutsideBar::Prices(void) { double h[2],l[2]; if(CopyHigh(m_symbol.Name(),m_period,1,2,h)!=2 || CopyLow(m_symbol.Name(),m_period,1,2,l)!=2) return(EMPTY_VALUE); //--- check of outside bar if(h[0] <= h[1] && l[0] >= l[1]) { m_order_open_long=h[1]+m_order_offset; m_order_stop_long=l[1]-m_order_offset; m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio; m_order_expiration_long=calcExpiration(); m_order_open_short=m_order_stop_long; m_order_stop_short=m_order_open_long; m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio; m_order_expiration_short=m_order_expiration_long; return(0); } return(EMPTY_VALUE); }
4.4. モジュールのパフォーマンステスト
次のセクションでは3例のモジュールが個別の確認、それから1件のエキスパートでの連携処理テストをされ、そのシステムが設計通りに動作することを確認します。結果、4件のエキスパートが作成されます。タイムフレーム H12 で動作するオーサムオシレータインディケータに基づくトレードシグナルモジュール は作成された各エキスパートでフィルターとして使用されます。価格モジュールは H6 で動作しています。
資金管理:固定ロットとトレーリングストップが使用されます。テストはすべて MetaQuotes-Demo サーバーのデモアカウントからの EURUSD クオートで期間2013.01.01~2014.06.01について行われます。ターミナルバージョン:5.00、ビルド 965 (2014年6月27日)
短くするためテスト結果はバランスプロットでのみ表示されます。特定の場合すべてでエキスパートの動作を得るにはそれで十分です。
4.4.1. デルタ ZigZag Iインディケータを基にしたモジュール検証
図6 デルタ ZigZag Iインディケータを基にした価格モジュール検証のバランスプロット
4.4.2. 『インサイドバー』パターンを基にしたモジュール検証
図7 インサイドバーを基にした価格モジュール検証のバランスプロット
図7を見るにあたり、この段階でのテストの目的はコードのパフォーマンスを確認することで勝利戦略を得ることではないことを念頭起きます。
4.4.3. 『アウトサイドバー』パターンを基にしたモジュール検証
図8 アウトサイドバーを基にした価格モジュール検証のバランスプロット
4.4.4. 作成した価格モジュールの共同作業検証
前のテスト結果を考慮しながら、デルタZigZag、インサイドバー、アウトサイドバーに基づく価格モジュールはそれぞれ1、0.5、0.7 の荷重係数を受け取りました。加重係数は価格モジュールのプロパティを決定します。
図9 3件の価格モジュールでエキスパートを検証する際のバランスプロット
検証の各段階で、価格プロットにおいて行われる処理はすべて注意深く分析されています。戦略エラーと偏差は誘発されませんでした。提案されるプログラムはすべて本稿に添付していますので、ご自身でテストすることができます。
5. 使用説明
3件の価格モジュールとフィルターとしてオーサムオシレータを伴うエキスパート例において作成された拡張を用いたエキスパート開発段階について考察していきます。
作成開始前に、ヘッダファイル"CExpertAdvanced.mqh"と"CExpertSignalAdvanced.mqh" はMQL5/Include/Expert フォルダ内のMetaTrader 5 ターミナルのカタログにあり、価格モジュールのファイルはフォルダ MQL5/Include/Expert/MySignalにあることを確認します。エキスパート起動前には、コンパイルされたインディケータが MQL5/Indicators フォルダにあることを確認します。この場合、それは"DeltaZigZag.ex5"です。添付のアーカイブではファイルはすべてその場所にあります。このアーカイブを MetaTrader 5 ターミナルのあるフォルダに解凍し、カタログのマージを確認する時間がかかるだけです。
5.1. エキスパートの作成
エキスパート作成を開始するには MetaEditor で『ファイル』→『新規』を選択します。
図10 MQL5 ウィザードを使用した新しいエキスパートの作成
新しいウィンドウで『Expert Advisor (作成)』を選択し『次へ』を押します。
図11 MQL5 ウィザードを使用したエキスパートの作成
それにより名前を指定したウィンドウが表示されます。この特殊な例では、エキスパートの名前は"TEST_EA_AO_DZZ_IB_OB"、パラメータSymbolとTimeFrameはデフォルト値となっています。それから『次へ』をクリックします。
図12 Expert Advisor の一般的特性
表示されたウィンドウで『追加する』を押してひとつずつインクルードモジュールを追加します。以下が全体手順です。
ご注意ください! モジュール追加の際、まずシグナルモジュール(継承者 CExpertSignal)をすべてインクルードし、それから価格モジュール(継承者CExpertSignalAdvanced)を追加します。モジュールをインクルードするこの順序を乱すと結果は予測できません。
それでオーサムオシレータからのシグナルモジュールのインクルードから始めます。この例ではこれが唯一のフィルターです。
図13 オーサムオシレータからのシグナルモジュールのインクルード
シグナルモジュールがすべてインクルードできたら、価格モジュールをインクルードします(順序はランダムで構いません)。この例では3つあります。
図14 DeltaZZ からのシグナルモジュールのインクルード
図15 インサイドバー価格モジュールからのシグナルモジュールのインクルード
図16 アウトサイドバー価格モジュールからのシグナルモジュールのインクルード
モジュールがすべて追加されたらウィンドウは以下のような表示となります。
図17 トレードシグナルのインクルード済みモジュールリスト
価格モジュールに対する『加重』パラメータ値は優先順位を決定します。
『次へ』ボタンを押したら、ウィザードは資金管理モジュールとトレーリングストップモジュールの選択を提案してきます。『次へ』または『準備完了』を押し、そのうちの1つを選択するか選択しないままにします。
『準備完了』を押すとアドバイザーの作成済みコードを受信します。
5.2. 作成済みコードの編集
というわけでコード取得しました。
1. 一番最初に文字列の検索をします。
#include <Expert\Expert.mqh>
そしてそれを次のように編集します。
#include <Expert\ExpertAdvanced.mqh>
作成したクラスのファイルをインクルードするためにこれが必要なのです。
2. そして文字列を検索します。
CExpert ExtExpert;
次のように変更します。
CExpertAdvanced ExtExpert;
これは必要な関数を持つ継承クラスのためにエキスパートの標準クラスを変更します。
3. 文字列を検索します。
CExpertSignal *signal=new CExpertSignal;
次のように変更します。
CExpertSignalAdvanced *signal=new CExpertSignalAdvanced;
これは必要な関数を持つ継承クラスのためにメインシグナルの標準クラスを変更する方法です。
4. メインシグナルの m_filters 配列に最初の価格モジュールの追加を実装する文字列を検索します。この例では以下です。
signal.AddFilter(filter1);
この前に文字列を挿入します。
signal.CalcPriceModuleIndex();
これはメインシグナルがm_filters 配列内のどのインデックスまでがシグナルモジュールポインターで、どこからが価格モジュールポインターなのか知るために必要なものです。
指定の文字列を挿入する正しい箇所を見つけることは難しいかもしれません。参照ポイントとして語『フィルター』の後に数字を使います。それは検索を簡単にし正しい位置を見逃さずにすみます。MQL5 ウィザードはインクルードされたモジュールにその順序で自動的に名前を付けます。妻女のモジュールは filter0、2番目は filter1、3番目はfilter2 などです。われわれの場合、シグナルモジュールは1個だけです。そのため、最初にインクルードされる価格モジュールはNo.2で、ゼロから始まるコードで番号付するフィルターを追加するため文字列"signal.AddFilter(filter1);" を検索する必要があります。その図解は図18です。
図18 インクルード順のコード内のモジュール名
5. このパートは強制ではありません。変更を取り入れても、エキスパートのパラメータは始値のくぼみ、ストップロス、テイクプロフィット、それらを無効にする注文期限にとっては重要なものです。コードをより簡潔にするため、以下の文字列を削除することができます。
input double Signal_PriceLevel =0.0; // Price level to execute a deal input double Signal_StopLevel =50.0; // Stop Loss level (in points) input double Signal_TakeLevel =50.0; // Take Profit level (in points) input int Signal_Expiration =4; // Expiration of pending orders (in bars)
上記文字列を削除することで発生するコンパイルエラーにより、以下の文字列グループ削除することになります。
signal.PriceLevel(Signal_PriceLevel); signal.StopLevel(Signal_StopLevel); signal.TakeLevel(Signal_TakeLevel); signal.Expiration(Signal_Expiration);
後者を削除したと、コンパイルは正常に行われます。
編集についてのコメントと説明はエキスパートの添付コード"TEST_EA_AO_DZZ_IB_OB"にあります。削除不能なコードの文字列にはコメントが入っています。
おわりに
本稿ではMQL5 ウィザードのアプリケーション領域を大幅に拡張しました。現在価格に関わらず異なる価格レベルでの発注、ストップロスおよびテイクプロフィットの設定が必要な自動トレーディングシステムの最適化作成に使用することができます。
一般化されたエキスパートは送信されている注文のパラメータを計算する価格モジュールセットをインクルードする可能性があります。もっとも適したパラメータセットは利用可能なものの中から選択されます。優先事項は設定で指定されます。それにより最大の効果を持つ数多くのさまざまなエントリーポイントを使用することができます。この方法がエキスパートの選択の幅を拡げます。方向がわかっているがエントリーポイントが決められていない場合、エキスパートはそれが出現するのを待ちます。
価格レベルを検索する標準互換モジュールを取り入れることは大きなメリットでエキスパートの作成をシンプルにすることを意味します。現時点ではモジュールは3個だけですが、その数は将来かならず増えることでしょう。本稿を有用だと思われたら、コメントに価格モジュール操作のアルゴリズムをご提案ください。コードにおもしろいアイデアが取り入れられることでしょう。
本稿はまたウィザードで作成されるエキスパートの機能をさらに拡張する方法も取り上げています。継承が導入する変更の最適な方法です。
プログラム技能を持たなくても説明に従ってコード編集が可能なユーザーのみなさんにとっては、利用可能なモデルを基にしたより高度なエキスパート作成のチャンスとなることでしょう。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/987
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索