English Русский 中文 Español Deutsch Português
個別のモジュールを使用したEAの構築

個別のモジュールを使用したEAの構築

MetaTrader 5エキスパートアドバイザー | 20 2月 2020, 10:04
1 040 0
Andrei Novichkov
Andrei Novichkov

イントロダクション

EAやスクリプトのインジケータを開発する際、開発者はトレーディング戦略と直接関係のないさまざまなコードを作成する必要があります。 たとえば、このようなコードは、EAの操作スケジュール(日、毎週、毎月)に関係する場合があります。 その結果、シンプルなインターフェイスを使用してトレードロジックや他のコンポーネントとデータのやり取りできる独立したプロジェクトを作成することになります。少なくとも、このようなスケジュールは、大きな変更を加えることなく、カスタム開発のEAやインジケータでさらに使用することができます。この記事では、ブロックを使用してEAの作成にシステムアプローチをします。 また、結果として浮かび上がる興味深い可能性も考察します。 この記事は初心者向けです。

通常のプラクティス(よく使う機能)とは何でしょうか。

このようなEAがどのように見えるか、また、どのような部品/コンポーネント/モジュールを含むことができるのかを理解してみましょう。 このようなコンポーネントはどこで取り上げられているでしょうか。 その答えはシンプルで明確です。アプリケーション開発の過程で、プログラマは、類似した関数や同じ関数を持つさまざまなコンポーネントを作成する必要があります。

同じ関数(トレーリングストップ関数など)をゼロから毎回実装する必要はないことは明らかです。 一般的に、トレーリングストップは類似の機能で実行でき、異なるEAに対して同様のインプットを行うことができます。 したがって、プログラマはトレーリングストップの関数コードを一度作成すれば、最小限の労力で必要なEAに挿入できます。 同じことは、トレードスケジュール、様々なニュースフィルタ、トレード関数を含むモジュールなど、他の多くのコンポーネントにも当てはまります。

したがって、別々のモジュール/ブロックを使用してEAを組み立てることができることに基づいて、構築セットのようなものがあります。 モジュールは、お互いに、そして決定を下す "ストラテジー"であるEAの"カーネル"と情報を交換します。 別々のモジュール間の可能な関係を表示しましょう:



結果のスキームはやや混乱するものです。 3 つのモジュールと 2 つのEAハンドラの相互作用のみを示しています。OnStart と OnTickです、 より複雑なEAでは、内部バインドはさらに複雑になります。 このようなEAは管理が難しいです。 さらに、いずれかのモジュールを除外する必要がある場合や、追加モジュールを追加する必要がある場合、困難を引き起こします。 また、初期のデバッグとトラブルシューティングは簡単ではありません。 このような理由の一つは、結合が適切な体系的なアプローチなしで設計されているという事実に関連します。 モジュールが必要な場合はいつでも、互いに、そしてEAハンドラと通信することを禁止し、特定のオーダーが現れます:

  • すべてのモジュールは OnInit で作成されます。
  • EAロジックは OnTick に含まれています。
  • モジュールは OnTick とのみ情報を交換します。
  • 必要に応じて、OnDeinit でモジュールが削除されます。

このようなシンプルな解決策は、迅速なポジティブな効果を期待することができます。 個別のモジュールは、接続/切断、デバッグ、変更が容易です。 EAコード全体の異なる場所にバインドが追加されるのではなく、1 つのハンドラで実装されている場合、OnTick のロジックは、メンテナンスと改善によりアクセスしやすくなります。


このマイナーなデザイン変更により、より明確なEA構造が実現し、直感的に操作できます。 新しい構造は "Observer" パターンの適用結果に似ていますが、構造自体はパターンとは異なります。 デザインをさらに改善する方法を見てみましょう。

実験のEA

アイデアをチェックするには、簡単なEAが必要です。 目的は関数を実証することだけなので、複雑なEAを必要としません。 前のローソク足が弱気の場合、このEAは売りオーダーを開きます。 このEAは、モジュラー構造を使用して設計されます。 最初のモジュールは、トレード関数を実装します。

class CTradeMod {
public:
   double dBaseLot;
   double dProfit;
   double dStop;
   long   lMagic;
   void   Sell();
   void   Buy();
};

void CTradeMod::Sell()
{
  CTrade Trade;
  Trade.SetExpertMagicNumber(lMagic);
  double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
  Trade.Sell(dBaseLot,NULL,0,ask + dStop,ask - dProfit);
} 

void CTradeMod::Buy()
{
  CTrade Trade;
  Trade.SetExpertMagicNumber(lMagic);
  double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
  Trade.Buy(dBaseLot,NULL,0,bid - dStop,bid + dProfit);
} 

このモジュールは、オープンフィールドとメソッドを持つクラスとして実装されます。 今のところ、モジュール内に実装されたBuy()メソッドは必要ありませんが、後で必要になります。 個別のフィールドの値は明確にする必要があります。ボリューム、トレードレベルとマジックに使用します。 モジュールの使い方: を作成し、エントリーシグナルが出現したときに Sell() メソッドを呼び出します。

別のモジュールがEAに含まれています:

class CParam {
public:
   double dStopLevel;      
   double dFreezeLevel;   
    CParam() {
      new_day();      
    }//EA_PARAM()
    void new_day() {
      dStopLevel   = SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL) * Point();
      dFreezeLevel = SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL) * Point();
    }//void new_day()
};

このモジュールについて詳しく考えてみましょう。 これは、他のモジュールやEAハンドラで使用する様々なパラメータを含む補助モジュールです。 次のようなコードが表示されている可能性があります。
...
input int MaxSpread = 100;
...
OnTick()
 {
   if(ask - bid > MaxSpread * Point() ) return;
....
 }

明らかにこのフラグメントは効果がありません。 更新または変換する必要があるすべてのインプット (およびその他の) パラメータを別のモジュールに追加すると (ここでは MaxSpread * Point() を扱います)、グローバル領域をクリーンに保ち、stops_levelとfreeze_levelで行われるように、グローバル領域を効率的に制御できます。

おそらく、モジュールフィールドを開くよりも、特別なゲッターを提供するのがより良い解決策でしょう。 ここでは、上記のソリューションを使用してコードを簡略化します。 実際のプロジェクトでは、ゲッターを使用する方が良いです。

さらに、CParam モジュールに対して例外を作成し、OnTick() ハンドラだけでなく、他のすべてのモジュールやハンドラからもこのモジュールにアクセスできるようにすることができます。

インプットブロックとEAハンドラを次に示します。

input double dlot   = 0.01;
input int    profit = 50;
input int    stop   = 50;
input long   Magic  = 123456;

CParam par;
CTradeMod trade;

int OnInit()
  {  
   trade.dBaseLot = dlot;
   trade.dProfit  = profit * _Point;
   trade.dStop    = stop   * _Point;
   trade.lMagic   = Magic;
   
   return (INIT_SUCCEEDED);
  }
  
void OnDeinit(const int reason)
  {
  }

void OnTick()
  {
   int total = PositionsTotal(); 
   ulong ticket, tsell = 0;
   ENUM_POSITION_TYPE type;
   double l, p;
   for (int i = total - 1; i >= 0; i--) {
      if ( (ticket = PositionGetTicket(i)) != 0) {
         if ( PositionGetString(POSITION_SYMBOL) == _Symbol) {
            if (PositionGetInteger(POSITION_MAGIC) == Magic) {
               type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
               l    = PositionGetDouble(POSITION_VOLUME);
               p    = PositionGetDouble(POSITION_PRICE_OPEN);
               switch(type) {
                  case POSITION_TYPE_SELL:
                     tsell = ticket;    
                     break;
               }//switch(type)
            }//if (PositionGetInteger(POSITION_MAGIC) == lmagic)
         }//if (PositionGetString(POSITION_SYMBOL) == symbol)
      }//if ( (ticket = PositionGetTicket(i)) != 0)
   }//for (int i = total - 1; i >= 0; i--)
   if (tsell == 0) 
      {
        double o = iOpen(NULL,PERIOD_CURRENT,1); 
        double c = iClose(NULL,PERIOD_CURRENT,1); 
        if (c < o) 
          {
            trade.Sell();
          }   
      }                         
  }

EAは OnInit() ハンドラでモジュールを初期化し、OnTick() ハンドラからのみアクセスします。 OnTick()では、このEAは、必要なポジションが既に開かれているかどうかを確認するために、オープンポジションをループします。 ポジションがまだ開いていない場合、シグナルがあればEAは開きます。

OnDeinit(const int reason)ハンドラは現在空であることに注意してください。 モジュールは明示的に削除する必要がないように作成されます。 また、ポジションオープンのチェックは行われないため、CParamはまだ使用されていません。 このようなチェックが実行された場合、CTradeModモジュールはCParamモジュールにアクセスする必要があり、開発者は前述の例外を作り、CParamへのアクセスを許可する必要があります。 ただし、この場合は必要ありません。

もっと詳しく見てみましょう。 CTradeModモジュールは、SLとTPレベルだけでなく、ポジションボリュームをチェックするためにCParamからのデータを必要とするかもしれません。 しかし、同じチェックは意思決定ポイントで実行することができます:レベルとボリュームが要件を満たしていない場合は、ポジションを開かないでください。 したがって、チェックは OnTick() ハンドラに移動されます。 例として、トレードレベルとボリューム値はインプットパラメータで指定されるため、OnInit()ハンドラで一度チェックを行うことができます。 チェックが失敗した場合、EA全体の初期化はエラーで終了する必要があります。 したがって、CTradeMod モジュールと CParam モジュールは独立して動作します。 これはほとんどのEAに関連しています:独立したモジュールはOnTick()ハンドラを介して動作し、お互いについて何も知りません。 ただし、この状態は、場合によっては観察できません。 これは後で検討します。

改善

最初に対処する問題は OnTick() ハンドラの大きなポジションループです。 開発者が次の場合は、このコードが必要です。

  1. エントリーシグナルがアクティブなままである場合は、ポジションオープンを避けます。
  2. 一時ストップまたは操作の中断後に、EAが既に発注されたオーダーを検出できるようにします。
  3. EAは、オープンポジションの総量や最大ドローダウンなどの現在の統計を収集できるようにします。

このループは、グリッドと平均化技術を使用するEAではさらに大きくなります。 また、EAが仮想トレードレベルを使用する場合は必須です。 したがって、このコードに基づいて別のモジュールを作成することをお勧めします。最もシンプルなケースでは、モジュールは特定のマジックナンバーを持つポジションを検出し、EAにそのようなポジションを通知します。より複雑な場合、モジュールは、ロギングや統計収集モジュールなどのよりシンプルなモジュールを含むカーネルとして実装される可能性があります。 この場合、EAはツリーのベースにOnInit() と OnTick() ハンドラを持つツリーのような構造を持ちます。 このようなモジュールは次のようになります。

class CSnap {
public:
           void CSnap()  {
               m_lmagic = -1; 
               m_symbol = Symbol();               
           }
   virtual void  ~CSnap() {}   
           bool   CreateSnap();
           long   m_lmagic;
           string m_symbol;
};

すべてのフィールドが再び開かれています。 実際、このコードにはマジックナンバーとEAが実行されているシンボルの名前の2つのフィールドがあります。 必要に応じて、フィールドの値を OnInit() ハンドラで設定できます。 タスクの主要部分は、CreateSnap() メソッドによって実行されます。
bool CSnap::CreateSnap() {
   int total = PositionsTotal(); 
   ulong ticket;
   ENUM_POSITION_TYPE type;
   double l, p;
   for (int i = total - 1; i >= 0; i--) {
      if ( (ticket = PositionGetTicket(i)) != 0) {
         if (StringLen(m_symbol) == 0 || PositionGetString(POSITION_SYMBOL) == m_symbol) {
            if (m_lmagic < 0 || PositionGetInteger(POSITION_MAGIC) == m_lmagic) {
               type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
               l    = PositionGetDouble(POSITION_VOLUME);
               p    = PositionGetDouble(POSITION_PRICE_OPEN);
               switch(type) {
                  case POSITION_TYPE_BUY:
// ???????????????????????????????????????
                     break;
                  case POSITION_TYPE_SELL:
// ???????????????????????????????????????
                     break;
               }//switch(type)
            }//if (lmagic < 0 || PositionGetInteger(POSITION_MAGIC) == lmagic)
         }//if (StringLen(symbol) == 0 || PositionGetString(POSITION_SYMBOL) == symbol)
      }//if ( (ticket = PositionGetTicket(i)) != 0)
   }//for (int i = total - 1; i >= 0; i--)
   return true;
}

このコードはシンプルですが、問題があります。 モジュールは、取得した情報をどこでどのように渡す必要があるでしょうか。直近のコードフラグメントに疑問符が付いている行には何を書くべきでしょうか。 これは単純に見えるかもしれません。 OnTick() ハンドラで CreateSnap() を呼び出す: このメソッドは、必要なタスクを実行し、結果を CSnap クラスフィールドに保存します。 次に、ハンドラはフィールドをチェックし、結論を導きます。

このソリューションは、最もシンプルなケースで実装できます。 加重平均値を計算する場合など、各ポジションパラメータを個別に処理する必要がある場合はどうすればよいでしょうか。 この場合、より汎用的なアプローチが必要であり、データは次のオブジェクトに転送されて、さらに処理されます。 このようなオブジェクトへの特別なポインタをCSnapで提供する必要があります。

CStrategy* m_strategy;
このファイルに値を割り当てるメソッド:
     bool SetStrategy(CStrategy* strategy) {
              if(CheckPointer(strategy) == POINTER_INVALID) return false;
                 m_strategy = strategy;
                 return true;
              }//bool SetStrategy(CStrategy* strategy)   

このオブジェクトでは、エントリーの決定だけでなく、他の決定も可能であるため、オブジェクト名 CStrategy が選択されました。 したがって、オブジェクトはEA全体の戦略を定義することができます。

さて、CreateSnap()メソッドの「スイッチ」は次のようになります。

               switch(type) {
                  case POSITION_TYPE_BUY:
                     m_strategy.OnBuyFind(ticket, p, l);
                     break;
                  case POSITION_TYPE_SELL:
                     m_strategy.OnSellFind(ticket, p, l);
                     break;
               }//switch(type

必要に応じて、予約オーダーや適切なメソッド呼び出しのスイッチを使用してコードを補完できます。 さらに、このメソッドを変更して、より多くのデータを収集できます。 CreateSnap()は、CSnapクラスからの潜在的な継承を提供する仮想メソッドとして実装することができます。 しかし、今回のケースでは必要ないので、よりシンプルなコードを使用します。

さらに、このようなオブジェクトへのポインタ (CStrategy* ポインタとして実装しました) は、現在のモジュールだけでなく役に立ちます。 EAの操作ロジックとのモジュールの潜在的な接続は、アクティブな計算を実行するモジュールに必要な場合があります。 したがって、基本クラスに特殊なフィールドと初期化メソッドを用意しましょう。

class CModule {
   public:
      CModule() {m_strategy = NULL;}
     ~CModule() {}
     virtual bool SetStrategy(CStrategy* strategy) {
                     if(CheckPointer(strategy) == POINTER_INVALID) return false;
                     m_strategy = strategy;
                     return true;
                  }//bool SetStrategy(CStrategy* strategy)   
   protected:   
      CStrategy* m_strategy;  
};


さらに、CModuleから継承されたモジュールを作成します。 場合によっては、余分なコードが発生する可能性があります。 しかし、この欠点は、そのようなポインタが本当に必要なモジュールによって補われます。 モジュールのいずれかがそのようなポインタを必要としない場合は、そのようなモジュールでSetStrategy(..)メソッドを呼び出しません。 基本モジュール クラスは、まだ未知のフィールドやメソッドを追加する場合にも役立ちます。 たとえば、次のメソッド (この例では実装されていません) が役に立つ可能性があります。
public:
   const string GetName() const {return m_sModName;}
protected:
         string m_sModName;


このメソッドは、トラブルシューティング、デバッグ、または情報パネルで使用できるモジュール名を返します。

ここで、重要な CStrategy クラスを実装する方法を見てみましょう。

EA戦略

前述のように、インプットの決定を行うオブジェクトでなければなりません。 このようなオブジェクトは、決済、変更、部分決済などに関する決定を行うこともできます。 オブジェクトがこのように呼ばれるのはそのためです。 明らかに、モジュールとして実装することはできません。意思決定オブジェクトが各EAで個別であることが基本的に重要です。 それ以外の場合、結果のEAは以前に開発されたものと同一になります。 したがって、このようなオブジェクトを一度開発して、モジュールで行われるように、すべてのEAに挿入することはできません。 しかし、それは今回の目的ではありません。 既に既知の事実を使用して、基本クラスの開発を開始してみましょう。

class CStrategy  {
public:
   virtual  void    OnBuyFind  (ulong ticket, double price, double lot) = 0;
   virtual  void    OnSellFind (ulong ticket, double price, double lot) = 0;       
};// class CStrategy


同じくらい簡単です。 CreateSnap()が必要なポジションを検出した場合に、呼び出すメソッドを2つ追加しました。 CStrategy は抽象クラスとして実装され、メソッドは仮想クラスとして宣言されます。 これは、オブジェクトが異なるEAで異なるためです。 したがって、基本クラスは継承にのみ使用でき、メソッドはオーバーライドされます。

次に、CStrategy.mqh ファイルを CModule.mqh ファイルに追加する必要があります。

#include "CStrategy.mqh"


その後、EAフレームワークは完成したと考え、さらなる強化と改善に進むことができます。

EA戦略の改善

CSnap オブジェクトは、仮想メソッドを使用して CStrategy オブジェクトにアクセスします。 しかし、CStrategy オブジェクトには他のメソッドが存在する必要があります。 戦略オブジェクトは、決定を下すことができる必要があります。 したがって、適切なシグナルが検出された場合は、インプットの推奨値を提供し、そのようなインプットを実行する必要があります。 CSnap オブジェクトが CreateSnap() メソッドなどを呼び出すメソッドが必要です。 CStrategy クラスにメソッドをいくつか追加してみましょう。

   virtual  string  Name() const     = 0;
   virtual  void    CreateSnap()     = 0;  
   virtual  bool    MayAndEnter()    = 0;  
   virtual  bool    MayAndContinue() = 0;       
   virtual  void    MayAndClose()    = 0;


条件付きのリストで、特定のEAに対して変更または拡張することができます。 メソッドの処理内容:

  • Name() - ストラテジー名を返します。
  • CreateSnap() - CSnap オブジェクトの同じメソッドを呼び出します。
  • MayAndEnter() — エントリーシグナルがあるかどうかをチェックし、シグナルがあるかどうかをインプットします。
  • MayAndContinue() — エントリーシグナルがあるかどうかをチェックし、シグナルがあれば再びインプットします。
  • MayAndClose() — 決済シグナルがあるかどうかをチェックし、そのようなシグナルがあればすべてのポジションを閉じます。

もちろん、このリストは完璧には程遠いです。 重要なメソッド、戦略の初期化メソッドを欠いています。 今回の目的は、CStrategyオブジェクト内のすべてのモジュールへのポインタを実装することなので、OnTick()と他のハンドラはCStrategyオブジェクトにのみアクセスし、他のモジュールの存在については何も知りません。 したがって、モジュールポインタは、CStrategy オブジェクトに追加する必要があります。 対応するオープンフィールドを提供して OnInit() ハンドラで初期化することはできません。 これについては後で説明します。

代わりに、初期化のメソッドを追加してみましょう:

virtual  bool    Initialize(CInitializeStruct* pInit) = 0;
  初期化オブジェクト CInitializeStruct を使用して、必要なポインタを格納します。 オブジェクトは、次のように CStrategy.mqh で説明します。
class CInitializeStruct {};

CStrategy と同様に、継承を目的とした空のクラスです。 準備タスクを完了し、実際のEAに進むことができます。

実用的な使用法

前のローソク足が弱気であれば、固定TPとSLレベルで売りポジションを開く:シンプルなロジックに従って動作するデモEAを作成してみましょう。 次のポジションは、前のポジションが閉じられるまで開かないようにしてください。

ほぼ準備ができているモジュールがあります。 CStrategyから派生したクラスを考えてみましょう:

class CRealStrat1 : public CStrategy   {
public:
   static   string  m_name;
                     CRealStrat1(){};
                    ~CRealStrat1(){};
   virtual  string  Name() const {return m_name;}
   virtual  bool    Initialize(CInitializeStruct* pInit) {
                        m_pparam = ((CInit1* )pInit).m_pparam;
                        m_psnap = ((CInit1* )pInit).m_psnap;
                        m_ptrade = ((CInit1* )pInit).m_ptrade;
                        m_psnap.SetStrategy(GetPointer(this));
                        return true;
                    }//Initialize(EA_InitializeStruct* pInit)
   virtual  void    CreateSnap() {
                        m_tb = 0;
                        m_psnap.CreateSnap();
                    }  
   virtual  bool    MayAndEnter();
   virtual  bool    MayAndContinue() {return false;}       
   virtual  void    MayAndClose()   {}
   virtual  bool    Stop()            {return false;}   
   virtual  void    OnBuyFind  (ulong ticket, double price, double lot) {}
   virtual  void    OnBuySFind (ulong ticket, double price, double lot) {}   
   virtual  void    OnBuyLFind (ulong ticket, double price, double lot) {}
   virtual  void    OnSellFind (ulong ticket, double price, double lot) {tb = ticket;}  
   virtual  void    OnSellSFind(ulong ticket, double price, double lot) {}   
   virtual  void    OnSellLFind(ulong ticket, double price, double lot) {}      
private:
   CParam*          m_pparam;
   CSnap*           m_psnap;  
   CTradeMod*       m_ptrade;   
   ulong            m_tb;            
};
static string CRealStrat1::m_name = "Real Strategy 1";

bool CRealStrat1::MayAndEnter() {
   if (tb != 0) return false;  
   double o = iOpen(NULL,PERIOD_CURRENT,1); 
   double c = iClose(NULL,PERIOD_CURRENT,1); 
   if (c < o) {
      m_ptrade.Sell();
      return true;
   }   
   return false;
} 

このEAコードはシンプルで説明は必要ありません。 一部だけを考えてみましょう。 CRealStrat1 クラスの CreateSnap() メソッドは、すでに開いている売りポジションのチケットを含むフィールドをリセットし、CSnap モジュールの CreateSnap() メソッドを呼び出します。 CSnapモジュールは、オープンポジションをチェックします。 このEAによって開かれた売りポジションが見つかった場合、モジュールは CStrategy クラスの OnSellFind(..) メソッドを呼び出します。 その結果、CRealStrat1 クラスのOnSellFind(...)メソッドが呼び出されます。 m_tbフィールドの値をもう一度変更します。 MayAndEnter() メソッドは、ポジションが既に開かれているのを確認し、新しいポジションを開きません。 CStrategy 基本クラスの他のメソッドは、その実装が空である理由である、EAで使用されていません。

もう 1 つの興味深い点は、Initialize(..) メソッドに関するものです。 このメソッドは、CRealStrat1クラスのポインタに、別々の決定を下すために必要な他のモジュールへのポインタを追加します。 CStrategy クラスは、CRealStrat1 クラスに必要なモジュールを認識しないため、空の CInitializeStruct クラスを使用します。 CRealStrat1 クラスを含むファイルに CInit1 クラスを追加します (必須ではありません)。 CInit1 は次の構造体から継承されます。

class CInit1: public CInitializeStruct {
public:
   bool Initialize(CParam* pparam, CSnap* psnap, CTradeMod* ptrade) {
   if (CheckPointer(pparam) == POINTER_INVALID || 
       CheckPointer(psnap)  == POINTER_INVALID) return false;   
      m_pparam = pparam; 
      m_psnap  = psnap;
      m_ptrade = ptrade;
       return true;
   }
   CParam* m_pparam;
   CSnap*  m_psnap;
   CTradeMod* m_ptrade;
};

クラス オブジェクトは、OnInit ハンドラで作成および初期化でき、CRealStrat1 クラス オブジェクトの適切なメソッドに渡すことができます。 したがって、別々のオブジェクトからなる複雑な構造があります。 しかし、OnTick()ハンドラのシンプルなインターフェイスを介して構造を操作することができます。

OnInit() および OnTick() ハンドラ

OnInit() ハンドラと、可能なグローバルオブジェクトのリストを次に示します。

CParam     par;
CSnap      snap;
CTradeMod  trade;

CStrategy* pS1;

int OnInit()
  {  
   ...
   pS1 = new CRealStrat1();
   CInit1 ci;
   if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) return (INIT_FAILED);
   pS1.Initialize(GetPointer(ci));      
   return (INIT_SUCCEEDED);
  }
  


  OnInit() ハンドラで作成されるオブジェクトは、CRealStrat1 クラスインスタンスの 1 つだけです。 CInit1 クラスオブジェクトで初期化されます。その後、オブジェクトは OnDeinit() ハンドラで破棄されます。
void OnDeinit(const int reason)
  {
      if (CheckPointer(pS1) != POINTER_INVALID) delete pS1;      
  }


結果として得られる OnTick() ハンドラは簡単です:
void OnTick()
  {
      if (IsNewCandle() ){
         pS1.CreateSnap();
         pS1.MayAndEnter();
         
      }    
  }


新しいローソク足の始値に既存のポジションを確認し、エントリーシグナルがあるかどうかを確認. シグナルがあり、EAがまだエントリーを実行していない場合は、新しいポジションを開きます。 ハンドラはシンプルなので、必要に応じて、その中に追加の "global" コードを追加できます。 たとえば、チャートの起動直後にトレードを開始するのではなく、ボタンクリックなどでユーザーの確認を待つようにEAに指示できます。

他のEA関数の一部についてはここでは説明していませんが、添付の zip アーカイブで使用できます。

したがって、別々のブリック、モジュールから成るEAを設計しました。 でもそれだけではありません。 このプログラミングアプローチによって提供される興味深い機会を見てみましょう。

次は?

さらに実装できる最初の事は、モジュールの動的な交換に関するものです。 シンプルなスケジュールは、より高度なスケジュールに置き換えることができます。 この場合、既存のプロパティやメソッドを追加するのではなく、スケジュールをオブジェクトとして置き換える必要があり、デバッグやメンテナンスが複雑になります。 別々のモジュールの"Debug"と"リリース"バージョンを提供することができ、モジュールを制御するためのマネージャーを作成します。  

しかし、さらに興味深い可能性があります。 プログラミングアプローチでは、動的EA戦略の置き換えを実装することができ、この場合はCRealStrat1クラスの置き換えとして実装されます。 結果として得られるEAには、トレンドトレードとレンジトレード戦略の2つの戦略を実装する2つのカーネルがあります。 さらに、アジアのセッション中にトレードに第3の戦略を追加することができます。 つまり、1つのEA内に複数のEAを実装し、動的に切り替えることができます。 やり方:

  1. CStrategy クラスから派生した決定ロジックを含むクラスを開発する (CRealStrat1 など)
  2. この新しい戦略を初期化するクラスを開発します。
  3. 作成されたファイルをプロジェクトに接続します。
  4. インプットパラメータに新しい変数を追加し、開始時にアクティブな戦略を設定します。
  5. ストラテジースイッチャー、すなわちトレードパネルを実装します。

例として、もう一つの戦略をデモEAに追加してみましょう。 繰り返しますが、コードを複雑にしないように、シンプルな戦略を準備します。 最初の戦略は売りポジションを開きました。 同様のロジックがあります: 前のローソク足が強気である場合, 固定TPとSLレベルで買いポジションを開きます。 次のポジションは、前のポジションが閉じられるまで開かないようにしてください。 2 番目の戦略コードは最初のコードとよく似ており、添付の zip アーカイブで使用できます。 新しい方針初期化クラスもアーカイブに含まれています。

インプットパラメータとハンドラを含むEAファイルに対してどのような変更を加えるべきかを考えてみましょう。

enum CSTRAT {
   strategy_1 = 1,
   strategy_2 = 2
};

input CSTRAT strt  = strategy_1;

CSTRAT str_curr = -1;

int OnInit()
  {  
   ...
   if (!SwitchStrategy(strt) ) return (INIT_FAILED);
   ...       
   return (INIT_SUCCEEDED);
  }
  
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
      if (id == CHARTEVENT_OBJECT_CLICK && StringCompare(...) == 0 ) {
         SwitchStrategy((CSTRAT)EDlg.GetStratID() );
      } 
  }  

bool SwitchStrategy(CSTRAT sr) {
   if (str_curr == sr) return true;
   CStrategy* s = NULL;
   switch(sr) {
      case strategy_1:
         {
            CInit1 ci;
            if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) return false;
            s = new CRealStrat1();
            s.Initialize(GetPointer(ci));  
         }
         break;
      case strategy_2:
         {
            CInit2 ci;
            if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) return false;
            s = new CRealStrat2();
            s.Initialize(GetPointer(ci));              
         }   
         break;
   }
   if (CheckPointer(pS1) != POINTER_INVALID) delete pS1;
   pS1 = s;    
   str_curr = sr;
   return true;
}

ストラテジースイッチャー関数SwitchStrategy(...)とOnChartEvent(...) ハンドラは、トレードパネルに接続されています。 そのコードはこの記事では提供されていませんが、添付のzipアーカイブで利用できます。 また、動的な戦略管理は複雑なタスクではありません。 ストラテジーで新しいオブジェクトを作成し、前のオブジェクトを削除し、変数に新しいポインタを書き込みます。

CStrategy* pS1;


その後、EAはOnTick()で新しい戦略にアクセスし、したがって新しい操作ロジックを使用します。 オブジェクトの階層と主な依存関係は次のようになります。

この図は、初期化中に2次として発生するトレードパネルとの接続を示していません。 この段階で、タスクは完了したとを考えることができます: このEAは、操作の準備完了です。 使用するブロックアプローチでは、重要な改善と修正をかなり短時間で行うことができます。

    結論

    標準設計パターンオブザーバとファサードの要素を使用してEAを設計しました。 パターン (およびその他の) パターンの完全な説明は、次の書籍に記載されています。 "Design Patterns.Elements of Reusable Object-Oriented Software(再利用可能なオブジェクト指向ソフトウェアの要素)" 著者:Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides(エーリッヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ヴリスサイド) この著書を読むことをお勧めします。

    この記事で使用したプログラム

     # 名前
    タイプ
     詳細
    1
    Ea&Modules.zip アーカイブ EAファイルを含むzipアーカイブ。


    MetaQuotes Ltdによってロシア語から翻訳されました。
    元の記事: https://www.mql5.com/ru/articles/7318

    添付されたファイル |
    EaxModules.zip (7.74 KB)
    戦略ビルダー機能の拡張 戦略ビルダー機能の拡張
    前の2つの記事では、さまざまなデータ型へのメリルパターンの適用について説明し、提示されたアイデアをテストするためのアプリケーションを開発しました。本稿では、引き続き戦略ビルダーで作業し、その効率を改善し、新しい機能を実装します。
    Boxplotによる金融時系列のシーズンパターンの探索 Boxplotによる金融時系列のシーズンパターンの探索
    この記事では、Boxplotを使用して価格時系列のシーズン特性を表示します。 各Boxplot(あるいは"ボックスアンドウイスキーダイアグラム") は、データセットに沿って値がどのように分布しているかを示す優れたものです。 Boxplotは、視覚的に似ていますが、ローソク足チャートと混同しないでください。
    MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第25部): 取引サーバから返されたエラーの処理 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第25部): 取引サーバから返されたエラーの処理
    サーバに取引注文を送信した後は、エラーコードやエラーがないことを確認する必要があります。本稿では、取引サーバによって返されるエラーの処理について考察し、未決取引リクエストを作成する準備をします。
    MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第24部): 基本取引クラス - 無効なパラメータの自動修正 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第24部): 基本取引クラス - 無効なパラメータの自動修正
    本稿では、無効な取引注文パラメータのハンドラを一瞥して、取引イベントクラスを改善します。これによって、すべての取引イベント(単一のイベントと1ティック内で同時に発生したイベントの両方)がプログラムで正しく定義されるようになります。