English Русский 中文 Español Deutsch Português
preview
多通貨エキスパートアドバイザーの開発(第4回):仮想注文の保留と状況の保存

多通貨エキスパートアドバイザーの開発(第4回):仮想注文の保留と状況の保存

MetaTrader 5トレーディングシステム | 12 8月 2024, 13:09
70 0
Yuriy Bykov
Yuriy Bykov

はじめに

前回の記事では、複数の並行戦略を持つ多通貨EAを構築するために、コードアーキテクチャを大幅に修正しました。シンプルでわかりやすくするために、これまでは、ある最低限の機能だけを考えてきました。私たちのタスクの限界を考慮して、以前の記事からコードを大幅に変更しました。

これで、すでに書かれているコードを抜本的に変更することなく、機能性を高めるのに十分な下地ができたと思います。本当に必要な部分のみ、最低限の編集をおこなうようにします。

さらなる発展として、以下のことに挑戦します。

  • 仮想ポジション(買い、売り)だけでなく、仮想予約注文(買い逆指値、売り逆指値、買い指値、売り指値)を出す機能を追加する
  • 発注された仮想注文とポジションを可視化する簡単な方法を追加し、使用する取引戦略のポジション/注文を建てるルールの正しい実装をテストする際に視覚的にコントロールできるようにする
  • EAが現在の状態データを保存することで、端末が再起動されたり、EAが別の端末に移動したりしても、作業が中断された時点の状態から作業を継続できるようにする

最も簡単なことから始めましょう。仮想予約注文の処理です。


仮想予約注文

仮想ポジションを扱うためにCVirtualOrderクラスを作成しました。仮想予約注文を処理するために、同様のクラスを別に作成することができます。ポジションの扱いが注文の扱いと本当に違うのか見てみましょう。

両者の性質はほぼ一致しています。ただし、予約注文には、有効期限を保存するプロパティが追加されています。そのため、期限切れという新たな終了理由が追加されました。従って、各ティックごとに、未決済の仮想予約注文がこの理由でクローズされたかどうかを確認し、ストップロスとテイクプロフィットのレベルに達したときは、クローズしないようにする必要があります。

価格が仮想予約注文のトリガーレベルに達すると、仮想ポジションになります。ここで、異なるクラスの形で実装された場合、仮想ポジションを作成し、トリガーされた仮想予約注文を削除するために、追加の作業をおこなわなければならないことがすでに明らかになっています。これらを1つのクラスとして実装すれば、クラスオブジェクトのプロパティを1つだけ変更すればよいのです。Typeです。したがって、まさにこのオプションを実装します。

注文とポジションには他にどのような違いがあるのでしょうか。仮想予約注文の始値も表示する必要もあります。仮想ポジションの場合、始値は現在の市場データから自動的に決定されるため、指定する必要はありません。次に、これを開始関数の必須パラメータにしましょう。

クラスに追加し始めましょう。次を追加します。

  • 有効期限を保存するためのm_expirationプロパティ
  • 予約注文の有効期限を示すm_isExpired論理プロパティ
  • 予約注文がトリガーされたかどうかを確認するCheckTrigger()メソッド、指定されたオブジェクトが特定のタイプ(予約注文、指値予約注文など)に属するかどうかを確認する複数のメソッド
  • 指値注文であるか逆指値注文であるかにかかわらず、希望する方向の予約注文である場合にもtrueが返されるという条件(指定されたオブジェクトが買い方向か売り方向かを確認するメソッドに追加) 
  • Open()メソッドのパラメータのリストで始値有効期限を受け取る

//+------------------------------------------------------------------+
//| Class of virtual orders and positions                            |
//+------------------------------------------------------------------+
class CVirtualOrder {
private:
   ...
//--- Order (position) properties
   ...
   datetime          m_expiration;     // Expiration time
   ...
   bool              m_isExpired;      // Expiration flag
   ...
//--- Private methods
   ...
   bool              CheckTrigger();   // Check for pending order trigger

public:
    ...

//--- Methods for checking the position (order) status
   ...
   bool              IsPendingOrder() {// Is it a pending order?
      return IsOpen() && (m_type == ORDER_TYPE_BUY_LIMIT
                          || m_type == ORDER_TYPE_BUY_STOP
                          || m_type == ORDER_TYPE_SELL_LIMIT
                          || m_type == ORDER_TYPE_SELL_STOP);
   }
   bool              IsBuyOrder() {    // Is it an open BUY position?
      return IsOpen() && (m_type == ORDER_TYPE_BUY
                          || m_type == ORDER_TYPE_BUY_LIMIT
                          || m_type == ORDER_TYPE_BUY_STOP);
   }
   bool              IsSellOrder() {   // Is it an open SELL position?
      return IsOpen() && (m_type == ORDER_TYPE_SELL 
                          || m_type == ORDER_TYPE_SELL_LIMIT
                          || m_type == ORDER_TYPE_SELL_STOP);
   }
   bool              IsStopOrder() {   // Is it a pending STOP order?
      return IsOpen() && (m_type == ORDER_TYPE_BUY_STOP || m_type == ORDER_TYPE_SELL_STOP);
   }
   bool              IsLimitOrder() {  // is it a pending LIMIT order?
      return IsOpen() && (m_type == ORDER_TYPE_BUY_LIMIT || m_type == ORDER_TYPE_SELL_LIMIT);
   }
   ...

//--- Methods for handling positions (orders)
   bool              CVirtualOrder::Open(string symbol,
                                         ENUM_ORDER_TYPE type,
                                         double lot,
                                         double price,
                                         double sl = 0,
                                         double tp = 0,
                                         string comment = "",
                                         datetime expiration = 0,
                                         bool inPoints = false); // Opening a position (order)

   ...
};

仮想ポジションを建てるOpen()メソッドで、m_openPriceプロパティに始値を代入します。このメソッドは、仮想予約注文も出します。予約注文ではなくポジションであることが判明した場合、プロパティに現在の市場始値を割り当てるようにします。

bool CVirtualOrder::Open(string symbol,         // Symbol
                         ENUM_ORDER_TYPE type,  // Type (BUY or SELL)
                         double lot,            // Volume
                         double price = 0,      // Open price
                         double sl = 0,         // StopLoss level (price or points)
                         double tp = 0,         // TakeProfit level (price or points)
                         string comment = "",   // Comment
                         datetime expiration = 0,  // Expiration time
                         bool inPoints = false  // Are the SL and TP levels set in points?
                        ) {
   ...

   if(s_symbolInfo.Name(symbol)) {  // Select the desired symbol
      s_symbolInfo.RefreshRates();  // Update information about current prices

      // Initialize position properties
      m_openPrice = price;
     ...
      m_expiration = expiration;

      // The position (order) being opened is not closed by SL, TP or expiration
      ...
      m_isExpired = false;

      ...
      // Depending on the direction, set the opening price, as well as the SL and TP levels.
      // If SL and TP are specified in points, then we first calculate their price levels
      // relative to the open price
      if(IsBuyOrder()) {
         if(type == ORDER_TYPE_BUY) {
            m_openPrice = s_symbolInfo.Ask();
         }
         ...
      } else if(IsSellOrder()) {
         if(type == ORDER_TYPE_SELL) {
            m_openPrice = s_symbolInfo.Bid();
         }
         ...
      }

      ...

      return true;
   }
   return false;
}

仮想予約注文がトリガーされたかどうかを確認するCheckTrigger()メソッドでは、注文の方向に応じて現在の市場価格BidまたはAskを取得し、それが希望する側の始値に達しているかどうかを確認します。達している場合、現在のオブジェクトのm_typeプロパティを、希望する方向のポジションに対応する値で置き換え、新しい仮想ポジションが開いたことを、受信者オブジェクトと戦略オブジェクトに通知します。

//+------------------------------------------------------------------+
//| Check whether a pending order is triggered                       |
//+------------------------------------------------------------------+
bool CVirtualOrder::CheckTrigger() {
   if(IsPendingOrder()) {
      s_symbolInfo.Name(m_symbol);     // Select the desired symbol
      s_symbolInfo.RefreshRates();     // Update information about current prices
      double price = (IsBuyOrder()) ? s_symbolInfo.Ask() : s_symbolInfo.Bid();
      int spread = s_symbolInfo.Spread();

      // If the price has reached the opening levels, turn the order into a position
      if(false
            || (m_type == ORDER_TYPE_BUY_LIMIT && price <= m_openPrice)
            || (m_type == ORDER_TYPE_BUY_STOP  && price >= m_openPrice)
        ) {
         m_type = ORDER_TYPE_BUY;
      } else if(false
                || (m_type == ORDER_TYPE_SELL_LIMIT && price >= m_openPrice)
                || (m_type == ORDER_TYPE_SELL_STOP  && price <= m_openPrice)
               ) {
         m_type = ORDER_TYPE_SELL;
      }

      // If the order turned into a position 
      if(IsMarketOrder()) {
         m_openPrice = price; // Remember the open price

         // Notify the recipient and the strategy of the position opening 
         m_receiver.OnOpen(GetPointer(this));
         m_strategy.OnOpen();
         return true;
      }
   }
   return false;
}

このメソッドは、これが実際に仮想予約注文である場合に、Tick()メソッドで新しいティックを処理するときに呼び出されます。

//+------------------------------------------------------------------+
//| Handle a tick of a single virtual order (position)               |
//+------------------------------------------------------------------+
void CVirtualOrder::Tick() {
   if(IsOpen()) {  // If this is an open virtual position or order
      if(CheckClose()) {  // Check if SL or TP levels have been reached
         Close();         // Close when reached
      } else if (IsPendingOrder()) {   // If this is a pending order
         CheckTrigger();  // Check if it is triggered
      }
   }
}

Tick()メソッドで、CheckClose()メソッドを呼び出します。ここで、有効期限に基づ いて仮想予約注文のクローズを確認するコードも追加する必要があります。

//+------------------------------------------------------------------+
//| Check the need to close by SL, TP or EX                          |
//+------------------------------------------------------------------+
bool CVirtualOrder::CheckClose() {
   if(IsMarketOrder()) {               // If this is a market virtual position,
      ...
      // Check that the price has reached SL or TP
      ...
   } else if(IsPendingOrder()) {    // If this is a pending order
      // Check if the expiration time has been reached, if one is specified 
      if(m_expiration > 0 && m_expiration < TimeCurrent()) {
         m_isExpired = true;
         return true;
      }
   }
   return false;
}

変更を現在のフォルダ内のVirtualOrder.mqhファイルに保存します。

次に、CSimpleVolumesStrategy取引戦略クラスに戻りましょう。この戦略では、仮想予約注文を処理するためのサポートを追加できるよう、将来の編集の余地を残しました。OpenBuyOrder()メソッドとOpenSellOrder()メソッドにそのような場所がありました。ここで、仮想予約注文を出すためのパラメータを指定してOpen()メソッドを呼び出すようにしましょう。まず現行値から始値を計算し、m_openDistanceパラメータで指定されたポイント数だけ希望の方向にステップバックします。ここでは、OpenBuyOrder()メソッドのコードのみを示します。別のコードでも、編集は似たようなものになるでしょう。

//+------------------------------------------------------------------+
//| Open BUY order                                                   |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::OpenBuyOrder() {
// Update symbol current price data
   ...
// Retrieve the necessary symbol and price data
   ...

// Let's make sure that the opening distance is not less than the spread
   int distance = MathMax(m_openDistance, spread);

// Opening price
   double price = ask + distance * point;

// StopLoss and TakeProfit levels
   ...

// Expiration time
   datetime expiration = TimeCurrent() + m_ordersExpiration * 60;

   ...
   for(int i = 0; i < m_maxCountOfOrders; i++) {   // Iterate through all virtual positions
      if(!m_orders[i].IsOpen()) {                  // If we find one that is not open, then open it
         if(m_openDistance > 0) {
            // Set SELL STOP pending order
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_STOP, m_fixedLot,
                                   NormalizeDouble(price, digits),
                                   NormalizeDouble(sl, digits),
                                   NormalizeDouble(tp, digits),
                                   "", expiration);

         } else if(m_openDistance < 0) {
            // Set SELL LIMIT pending order
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_LIMIT, m_fixedLot,
                                   NormalizeDouble(price, digits),
                                   NormalizeDouble(sl, digits),
                                   NormalizeDouble(tp, digits),
                                   "", expiration);

         } else {
            // Open a virtual SELL position
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
                                   0,
                                   NormalizeDouble(sl, digits),
                                   NormalizeDouble(tp, digits));

         }
         break; // and exit
      }
   }
  ...
}

現在のフォルダのSimpleVolumesStrategy.mqhファイルに変更を保存します。

これにより、仮想予約注文を持つ戦略の運用をサポートするために必要な変更が完了しました。2つのファイルだけを変更し、SimpleVolumesExpertSingle.mq5 EAをコンパイルできるようになりました。openDistance_パラメータを0以外に設定した場合、EAは仮想ポジションを建てる代わりに仮想予約注文を出す必要があります。しかし、チャート上では開始を見ることはできません。ログに適切なメッセージが表示されるだけです。チャート上でそれらを見ることができるのは、仮想取引量の受信者のオブジェクトによって市場にもたらされる仮想ポジションに変換された後にのみです。

発注された仮想予約注文をチャート上で見ることができればいいです。この問題については後で少し触れたいと思いますが、ここではより重要な問題、つまり再起動後にEAの状態が確実に保存され、読み込まれるようにすることに話を移しましょう。


ステータス保存

開発したクラスを基に2つのEAを作成しました。最初のもの(SimpleVolumesExpertSingle.mq5)は、取引戦略の単一のインスタンスのパラメータを最適化するためのもので、2番目のもの(SimpleVolumesExpert.mq5)には、最初のEAを使用して選択された最適なパラメータを持つ取引戦略の複数のコピーが既に含まれています。将来的には、2つ目のEAのみが実際の口座で使用され、1つ目のEAはストラテジーテスターでのみ使用される予定です。したがって、2つ目のEAやその他のEAでは、ステータスを読み込んで保存するだけで大丈夫です。これには、取引戦略の多くの例も含まれます。

次に、今お話しているのはEAの状態の保存と読み込みのことであり、EAで使用されるべき戦略の異なるセットのことではありません。言い換えれば、EA内の指定されたパラメータを持つ戦略インスタンスのセットは固定されており、起動するたびに同じになります。最初の起動後、EAは仮想ポジションと実ポジションを建て、おそらく価格データに基づいていくつかの指標を計算します。EAのステータスを構成するのは、この情報です。ここで端末を再起動すると、EAはポジションを自身のものとして認識し、すべての仮想ポジションと必要な計算値を復元するはずです。ポジションに関する情報は端末から取得できますが、EAは仮想ポジションと計算値に関する情報を独立して保存する必要があります。

これまで考えてきたシンプルな取引戦略では、計算されたデータを蓄積する必要がないため、EAの状態はすべて仮想ポジションと予約注文のセットによってのみ決定されます。ただし,すべてのCVirtualOrderクラスオブジェクトの配列だけをファイルに保存しても不十分です。

異なる取引戦略を持つ複数のEAがすでにあるとしましょう。それぞれのEAによって作成されたCVirtualOrderクラスオブジェクトの総数が同じであるとします。例えば、3つの仮想ポジションオブジェクトを要求する9つの取引戦略のインスタンスを使用したとします。各EAは27個のCVirtualOrderクラスオブジェクトの情報を格納します。この場合、あるEAが自分の27の仮想ポジションの情報ではなく、他のEAの情報をアップロードしてしまうことを、どうにかして防ぐ必要があります。 

そのためには、このEAの一部として動作する戦略のパラメータに関する情報と、場合によってはEA自体のパラメータに関する情報を保存ファイルに追加します。

では、どの時点で状態を保存すべきかを考えてみましょう。戦略パラメータが固定されていれば、いつでも同じです。同時に、仮想ポジションのオブジェクトは、開閉操作中にプロパティの値を変更することができます。つまり、これらの操作の後にステータスを保存することは理にかなっています。もっと頻繁に(たとえば、ティックごとやタイマーで)保存するのは、今のところ冗長なようです。

それでは実装を始めましょう。オブジェクトの階層構造は次の通りです。

  • EA
    • 戦略
      • 仮想ポジション

トップレベルにすべてを集中させるのは可能ですが、代わりに各レベルに保存の一部を委ねます。

各レベルで、Save()クラスとLoad()クラスに、それぞれ保存と読み込みを担当する2つのメソッドを追加します。最上位レベルでは、これらのメソッドはファイルを開き、下位レベルでは、すでに開いているファイルのディスクリプタをパラメータとして取得します。従って、保存するファイル名を決める問題は、EAレベルだけに残しておきます。この問題は、戦略や仮想ポジションのレベルでは生じません。


EA修正

CVirtualAdvisorクラスは、EA名を格納するためのm_nameフィールドを取得します。操作中に変更されることはないので、コンストラクタで代入します。また、EAをビジュアルテストモードで起動する場合に備えて、マジックナンバーとオプションの「.test」接尾辞を追加して名前を直ちに拡張することも理にかなっています。

仮想ポジションの構成が変更された場合にのみ保存を実行するには、m_lastSaveTimeフィールドを追加します。これは、最後に保存した時刻を保存します。 

//+------------------------------------------------------------------+
//| Class of the EA handling virtual positions (orders)              |
//+------------------------------------------------------------------+
class CVirtualAdvisor : public CAdvisor {
protected:
   ...
   
   string            m_name;           // EA name
   datetime          m_lastSaveTime;   // Last save time

public:
   CVirtualAdvisor(ulong p_magic = 1, string p_name = ""); // Constructor
   ...

   virtual bool      Save();           // Save status
   virtual bool      Load();           // Load status
};

エキスパートアドバイザーを作成する際、以下のように2つの新しいプロパティに初期値を割り当てます。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualAdvisor::CVirtualAdvisor(ulong p_magic = 1, string p_name = "") :
   ...
   m_lastSaveTime(0) {
   m_name = StringFormat("%s-%d%s.csv",
                         (p_name != "" ? p_name : "Expert"),
                         p_magic,
                         (MQLInfoInteger(MQL_TESTER) ? ".test" : "")
                        );
};

保存が必要かどうかを確認するロジックを、Save()メソッド内に配置します。各ティックに対して残りのアクションを実行した後に、このメソッドの呼び出しを追加するだけです。

//+------------------------------------------------------------------+
//| OnTick event handler                                             |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Tick(void) {
// Receiver handles virtual positions
   m_receiver.Tick();

// Start handling in strategies
   CAdvisor::Tick();

// Adjusting market volumes
   m_receiver.Correct();

// Save status
   Save();
}

saveメソッドでは、まず実行する必要があるかどうかを確認する必要があります。そのためには、受信者オブジェクトに新しいプロパティを追加し、仮想ポジションの最終変更時刻または実ポジションの最終修正時刻を格納することに、あらかじめ合意しておく必要があります。最後に保存した時間が最後に修正した時間より短くなった場合は、変更が生じているので、それを保存する必要があります。

また、ビジュアルモードを使用せずに最適化または単一テストを実施中の場合は、変更を保存しません。ビジュアルモードでテスト中であれば、保存が実行されます。これで、ストラテジーテスターでも保存を確認できるようになります。

EA レベルでのSave()は次のようになります。保存の必要性を確認し、現在の時間と戦略数を保存します。その後、すべての戦略の保存メソッドをループで呼び出します。

//+------------------------------------------------------------------+
//| Save status                                                      |
//+------------------------------------------------------------------+
bool CVirtualAdvisor::Save() {
   bool res = true;

   // Save status if:
   if(true
         // later changes appeared
         && m_lastSaveTime < CVirtualReceiver::s_lastChangeTime
         // currently, there is no optimization
         && !MQLInfoInteger(MQL_OPTIMIZATION)
         // and there is no testing at the moment or there is a visual test at the moment
         && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
     ) {
      int f = FileOpen(m_name, FILE_CSV | FILE_WRITE, '\t');

      if(f != INVALID_HANDLE) {  // If file is open, save
         FileWrite(f, CVirtualReceiver::s_lastChangeTime);  // Time of last changes
         FileWrite(f, ArraySize(m_strategies));             // Number of strategies

         // All strategies
         FOREACH(m_strategies, ((CVirtualStrategy*) m_strategies[i]).Save(f));

         FileClose(f);

         // Update the last save time
         m_lastSaveTime = CVirtualReceiver::s_lastChangeTime;
         PrintFormat(__FUNCTION__" | OK at %s to %s",
                     TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS), m_name);
      } else {
         PrintFormat(__FUNCTION__" | ERROR: Operation FileOpen for %s failed, LastError=%d",
                     m_name, GetLastError());
         res = false;
      }
   }
   return res;
}

戦略を保存した後、仮想ポジションの最終変更時刻に合わせて最終保存時刻を更新します。これで、保存メソッドは次の変更までファイルに何も保存しません。

Load()ステータス読み込みメソッドも同様に動作しますが、書き込む代わりにファイルからデータを読み込みます。つまり、まず保存した時刻と作戦の数を読み取ります。ここで、念のため、読み込んだ戦略の数がEAに追加した戦略の数と一致しているかどうかを確認することができます。一致していないなら、ファイルが間違っているので、これ以上読んでも意味がありません。一致しているなら、大丈夫なので、読み続けます。次に、後続の作業を階層の次のレベルのオブジェクトに再度委任します。追加されたすべての戦略を調べ、開いているファイルからそれらの読み取りメソッドを呼び出します。

コードでは次のようになります。

//+------------------------------------------------------------------+
//| Load status                                                      |
//+------------------------------------------------------------------+
bool CVirtualAdvisor::Load() {
   bool res = true;

   // Load status if:
   if(true
         // file exists
         && FileIsExist(m_name)
         // currently, there is no optimization
         && !MQLInfoInteger(MQL_OPTIMIZATION)
         // and there is no testing at the moment or there is a visual test at the moment
         && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
     ) {
      int f = FileOpen(m_name, FILE_CSV | FILE_READ, '\t');

      if(f != INVALID_HANDLE) {  // If the file is open, then load
         m_lastSaveTime = FileReadDatetime(f);     // Last save time
         PrintFormat(__FUNCTION__" | LAST SAVE at %s", TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));

         // Number of strategies
         long f_strategiesCount = StringToInteger(FileReadString(f));

         // Does the loaded number of strategies match the current one?
         res = (ArraySize(m_strategies) == f_strategiesCount);

         if(res) {
            // Load all strategies
            FOREACH(m_strategies, res &= ((CVirtualStrategy*) m_strategies[i]).Load(f));

            if(!res) {
               PrintFormat(__FUNCTION__" | ERROR loading strategies from file %s", m_name);
            }
         } else {
            PrintFormat(__FUNCTION__" | ERROR: Wrong strategies count (%d expected but %d found in file %s)",
                        ArraySize(m_strategies), f_strategiesCount, m_name);
         }
         FileClose(f);
      } else {
         PrintFormat(__FUNCTION__" | ERROR: Operation FileOpen for %s failed, LastError=%d", m_name, GetLastError());
         res = false;
      }
   }
   return res;
}

VirtualAdvisor.mqhファイルに加えられた変更を現在のフォルダに保存します。

EAを起動するときに、ステータス読み込みメソッドを一度だけ呼び出す必要がありますが、この時点ではまだEAに戦略が追加されていないため、EAオブジェクトのコンストラクタでこれをおこなうことはできません。すべての戦略インスタンスをEAオブジェクトに追加した後、OnInit()関数のEAファイル内で実行しましょう。

CVirtualAdvisor     *expert;                  // EA object

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// Create and fill the array of strategy instances
   CStrategy *strategies[9];
   strategies[0] = ...
   ...
   strategies[8] = ...

// Create an EA handling virtual positions
   expert = new CVirtualAdvisor(magic_, "SimpleVolumes");

// Add strategies to the EA
   FOREACH(strategies, expert.Add(strategies[i]));

// Load the previous state if available   
   expert.Load();

   return(INIT_SUCCEEDED);
}

現在のフォルダのSimpleVolumesExpert.mq5ファイルに変更を保存します。


基本戦略の修正

現在の戦略オブジェクトを文字列に変換するメソッドと同様に、Save()メソッドとLoad()メソッドを取引戦略の基本クラスに追加します。簡潔にするために、このメソッドをオーバーロードされた単項演算子~(チルダ)として実装してみましょう。

//+------------------------------------------------------------------+
//| Class of a trading strategy with virtual positions               |
//+------------------------------------------------------------------+
class CVirtualStrategy : public CStrategy {
   ...

public:
   ...

   virtual bool      Load(const int f);   // Load status
   virtual bool      Save(const int f);   // Save status

   string operator~();                    // Convert object to string
};

オブジェクトを文字列に変換するメソッドは、現在のクラス名と仮想ポジション配列の要素数を文字列で返します。

//+------------------------------------------------------------------+
//| Convert an object to a string                                    |
//+------------------------------------------------------------------+
string CVirtualStrategy::operator~() {
   return StringFormat("%s(%d)", typename(this), ArraySize(m_orders));
}

書き込み用に開かれたファイルのディスクリプタがSave()メソッドに渡されます。文字列に変換する際にオブジェクトから得られる文字列は、まずファイルに書き込まれます。そして、その保存メソッドが各仮想ポジションに対してループで呼び出されます。

//+------------------------------------------------------------------+
//| Save status                                                      |
//+------------------------------------------------------------------+
bool CVirtualStrategy::Save(const int f) {
   bool res = true;
   FileWrite(f, ~this); // Save parameters

   // Save virtual positions (orders) of the strategy
   FOREACH(m_orders, res &= m_orders[i].Save(f));

   return res;
}

Load()メソッドは、ファイルのパラメータ文字列と戦略のパラメータ文字列が一致していることを確認しながら、単純に、書き込まれたのと同じ順序でデータを読み込みます。

//+------------------------------------------------------------------+
//| Load status                                                      |
//+------------------------------------------------------------------+
bool CVirtualStrategy::Load(const int f) {
   bool res = true;
   // Current parameters are equal to read parameters   
   res = (~this == FileReadString(f));
   
   // If yes, then load the virtual positions (orders) of the strategy
   if(res) {
      FOREACH(m_orders, res &= m_orders[i].Load(f));
   }

   return res;
}

現在のフォルダのVirtualStrategy.mqhファイルに変更を保存します。


取引戦略の変更

特定の取引戦略のCSimpleVolumesStrategyクラスに、基本クラスと同じメソッドセットを追加する必要があります。

//+------------------------------------------------------------------+
//| Trading strategy using tick volumes                               |
//+------------------------------------------------------------------+
class CSimpleVolumesStrategy : public CVirtualStrategy {
   ...

public:
   ...

   virtual bool      Load(const int f) override;   // Load status
   virtual bool      Save(const int f) override;   // Save status

   string operator~();                    // Convert object to string
};

取引戦略を文字列に変換する方法では、クラス名から結果を生成し、すべての重要なパラメータをリストアップします。分かりやすくするために、現時点ではすべてのパラメータの値をここに追加しますが、将来的には、このリストを短縮した方が便利かもしれません。そうすれば、以前に開いていたすべてのマーケットポジションを完全に閉じることなく、パラメータを少し変更してEAを再開することができます。例えば、TakeProfitパラメータを大きくすれば、TakeProfitレベルを下げたポジションを安全に残すことができます。

//+------------------------------------------------------------------+
//| Convert an object to a string                                    |
//+------------------------------------------------------------------+
string CSimpleVolumesStrategy::operator~() {
   return StringFormat("%s(%s,%s,%.2f,%d,%.2f,%.2f,%d,%.2f,%.2f,%d,%d)",
                       // Strategy instance parameters
                       typename(this), m_symbol, EnumToString(m_timeframe), m_fixedLot,
                       m_signalPeriod, m_signalDeviation, m_signaAddlDeviation,
                       m_openDistance, m_stopLevel, m_takeLevel, m_ordersExpiration,
                       m_maxCountOfOrders
                      );
}

ここもまた、戦略のパラメータをすべてコードに書く必要がある場所であることが判明しました。入力で作業するまで、そのことを覚えておきます。

Save()メソッドは、基本クラスが主な作業をおこなうので、非常に簡潔であることがわかります。

//+------------------------------------------------------------------+
//| Save status                                                      |
//+------------------------------------------------------------------+
bool CSimpleVolumesStrategy::Save(const int f) {
   bool res = true;
   FileWrite(f, ~this);                // Save parameters
   res &= CVirtualStrategy::Save(f);   // Save strategy
   return res;
}

Load()メソッドは若干大きくなりますが、主にコードの可読性向上のためです。

//+------------------------------------------------------------------+
//| Load status                                                      |
//+------------------------------------------------------------------+
bool CSimpleVolumesStrategy::Load(const int f) {
   bool res = true;
   string currentParams = ~this;             // Current parameters
   string loadedParams = FileReadString(f);  // Read parameters

   PrintFormat(__FUNCTION__" | %s", loadedParams);

   res = (currentParams == loadedParams);

   // Load if read parameters match the current ones
   if(res) {
      res &= CVirtualStrategy::Load(f);
   }

   return res;
}

現在のフォルダのSimpleVolumesExpert.mqhファイルに変更を保存します。


仮想ポジションの変更

仮想ポジションのCVirtualOrderクラスに3つのメソッドを追加する必要があります。

class CVirtualOrder {
   ...

   virtual bool      Load(const int f);   // Load status
   virtual bool      Save(const int f);   // Save status

   string            operator~();         // Convert object to string
};

文字列に変換するメソッドは、ファイルに保存するときには使用しませんが、読み込んだオブジェクトデータをログに記録するときには便利です。

//+------------------------------------------------------------------+
//| Convert an object to a string                                    |
//+------------------------------------------------------------------+
string CVirtualOrder::operator~() {
   if(IsOpen()) {
      return StringFormat("#%d %s %s %.2f in %s at %.5f (%.5f, %.5f). %s, %f",
                          m_id, TypeName(), m_symbol, m_lot,
                          TimeToString(m_openTime), m_openPrice,
                          m_stopLoss, m_takeProfit,
                          TimeToString(m_closeTime), m_closePrice);
   } else {
      return StringFormat("#%d --- ", m_id);
   }

}

ただし、文字列からオブジェクトのプロパティを読み取るメソッドを追加することで、後でこれを変更するかもしれません。

最後に、Save()メソッドで、より重要な情報をファイルに書き込みます。

//+------------------------------------------------------------------+
//| Save status                                                      |
//+------------------------------------------------------------------+
bool CVirtualOrder::Save(const int f) {
   FileWrite(f, m_id, m_symbol, m_lot, m_type, m_openPrice,
             m_stopLoss, m_takeProfit,
             m_openTime, m_closePrice, m_closeTime,
             m_expiration, m_comment, m_point);
   return true;
}

Load()は、書き込まれた内容を読み取り、読み取った情報で必要なプロパティを埋めるだけでなく、関連する戦略オブジェクトと受信者に、この仮想ポジション(注文)がオープンかクローズかを通知します。

//+------------------------------------------------------------------+
//| Load status                                                      |
//+------------------------------------------------------------------+
bool CVirtualOrder::Load(const int f) {
   m_id = (ulong) FileReadNumber(f);
   m_symbol = FileReadString(f);
   m_lot = FileReadNumber(f);
   m_type = (ENUM_ORDER_TYPE) FileReadNumber(f);
   m_openPrice = FileReadNumber(f);
   m_stopLoss = FileReadNumber(f);
   m_takeProfit = FileReadNumber(f);
   m_openTime = FileReadDatetime(f);
   m_closePrice = FileReadNumber(f);
   m_closeTime = FileReadDatetime(f);
   m_expiration = FileReadDatetime(f);
   m_comment = FileReadString(f);
   m_point = FileReadNumber(f);

   PrintFormat(__FUNCTION__" | %s", ~this);

// Notify the recipient and the strategy that the position (order) is open
   if(IsOpen()) {
      m_receiver.OnOpen(GetPointer(this));
      m_strategy.OnOpen();
   } else {
      m_receiver.OnClose(GetPointer(this));
      m_strategy.OnClose();
   }

   return true;
}

取得したコードを現在のフォルダのVirtualOrder.mqhファイルに保存します。


保存のテスト

それでは、ステータスデータの保存と読み込みをテストしてみましょう。ポジションを建てるのに有利なタイミングを待つのを避けるため、取引戦略を一時的に変更し、まだ未決済のポジションや注文がない場合は、開始時に1つのポジションまたは未決済の注文を建てるようにします。

読み込みの進捗に関するメッセージの表示を追加したので、EAを再起動すると(これをおこなう最も簡単な方法は、単に再コンパイルすることです)、ログに以下のようなものが表示されます。

CVirtualAdvisor::Load | LAST SAVE at 2027.02.23 08:05:33

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(EURGBP,PERIOD_H1,0.06,13,0.30,1.00,0,10500.00,465.00,1000,3)
CVirtualOrder::Load | Order#1 EURGBP 0.06 BUY in 2027.02.23 08:02 at 0.85494 (0.75007, 0.85985). 1970.01.01 00:00, 0.000000
CVirtualReceiver::OnOpen#EURGBP | OPEN VirtualOrder #1
CVirtualOrder::Load | Order#2  ---
CVirtualOrder::Load | Order#3  ---

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(EURGBP,PERIOD_H1,0.11,17,1.70,0.50,210,16500.00,220.00,1000,3)
CVirtualOrder::Load | Order#4 EURGBP 0.11 BUY STOP in 2027.02.23 08:02 at 0.85704 (0.69204, 0.85937). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#5  ---
CVirtualOrder::Load | Order#6  ---

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(EURGBP,PERIOD_H1,0.06,51,0.50,1.10,500,19500.00,370.00,22000,3)
CVirtualOrder::Load | Order#7 EURGBP 0.06 BUY STOP in 2027.02.23 08:02 at 0.85994 (0.66494, 0.86377). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#8  ---
CVirtualOrder::Load | Order#9  ---

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(GBPUSD,PERIOD_H1,0.04,80,1.10,0.20,0,6000.00,1190.00,1000,3)
CVirtualOrder::Load | Order#10 GBPUSD 0.04 BUY in 2027.02.23 08:02 at 1.26632 (1.20638, 1.27834). 1970.01.01 00:00, 0.000000
CVirtualReceiver::OnOpen#GBPUSD | OPEN VirtualOrder #10
CVirtualOrder::Load | Order#11  ---
CVirtualOrder::Load | Order#12  ---

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(GBPUSD,PERIOD_H1,0.11,128,2.00,0.90,220,2000.00,1170.00,1000,3)
CVirtualOrder::Load | Order#13 GBPUSD 0.11 BUY STOP in 2027.02.23 08:02 at 1.26852 (1.24852, 1.28028). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#14  ---
CVirtualOrder::Load | Order#15  ---

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(GBPUSD,PERIOD_H1,0.07,13,1.50,0.80,550,2500.00,1375.00,1000,3)
CVirtualOrder::Load | Order#16 GBPUSD 0.07 BUY STOP in 2027.02.23 08:02 at 1.27182 (1.24682, 1.28563). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#17  ---
CVirtualOrder::Load | Order#18  ---

CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(EURUSD,PERIOD_H1,0.04,24,0.10,0.30,330,7500.00,2400.00,24000,3)
CVirtualOrder::Load | Order#19 EURUSD 0.04 BUY STOP in 2027.02.23 08:02 at 1.08586 (1.01086, 1.10990). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#20  ---
CVirtualOrder::Load | Order#21  ---
CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(EURUSD,PERIOD_H1,0.05,18,0.20,0.40,220,19500.00,1480.00,6000,3)
CVirtualOrder::Load | Order#22 EURUSD 0.05 BUY STOP in 2027.02.23 08:02 at 1.08476 (0.88976, 1.09960). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#23  ---
CVirtualOrder::Load | Order#24  ---
CSimpleVolumesStrategy::Load | class CSimpleVolumesStrategy(EURUSD,PERIOD_H1,0.05,128,0.70,0.30,550,3000.00,170.00,42000,3)
CVirtualOrder::Load | Order#25 EURUSD 0.05 BUY STOP in 2027.02.23 08:02 at 1.08806 (1.05806, 1.08980). 1970.01.01 00:00, 0.000000
CVirtualOrder::Load | Order#26  ---
CVirtualOrder::Load | Order#27  ---

CVirtualAdvisor::Save | OK at 2027.02.23 08:19:48 to SimpleVolumes-27182.csv

ご覧のように、仮想ポジションと予約注文のデータが正常に読み込まれています。仮想ポジションの場合、オープンイベントハンドラが呼び出され、必要であれば、実際のマーケットポジションをオープンします。

一般的に、再スタート中のポジションに関連するEAの動作は、かなり複雑な問題です。例えば、マジックナンバーを変更したい場合、EAは以前に開いた取引を自身の取引と見なしません。強制的にクローズすべきでしょうか。EAのバージョンを置き換える場合、保存されている仮想ポジションの存在を完全に無視し、すべてのポジションを強制的にクローズする必要があるかもしれません。どのシナリオが一番自分に合っているかは、その都度決めるべきです。これらの問題はまだそれほど切迫したものではないので、後回しにします。


可視化

次に、仮想ポジションと予約注文を可視化します。一見したところ、CVirtualOrder仮想ポジションクラスの拡張として実装するのがごく自然に思えます。可視化されたオブジェクトに関する必要な情報はすべてすでに持っています。自らを再描画する必要があるときは、誰よりもよく知っています。実は、最初のドラフト実装はまさにこの方法でおこなわれました。しかし、その後、非常に不愉快な疑問が浮かび上がってきました。

そのひとつが、どのチャートで可視化をおこなう予定かという疑問です。最もシンプルな答えは「現在のチャート」ですが、これはEAが1つの銘柄で動作し、それがEAが起動されたチャート銘柄と一致する場合にのみ適しています。銘柄が複数になると、すべての仮想ポジションを銘柄チャートに表示するのは非常に不便になります。チャートがぐちゃぐちゃになります。

言い換えれば、どのチャートに表示するかを選択するという問題には解決策が必要でしたが、それはもはやCVirtualOrderクラスのオブジェクトの主要な機能とは関係ありませんでした。可視化なしでも十分に機能しました。

したがって、このクラスは放っておいて、少し先を見てみましょう。もう少し目的を広げると、戦略やタイプごとにグループ化された仮想ポジションの表示を選択的に有効/無効にできるようになるといいでしょう。また、より詳細なデータ、例えば、始値、ストップロスとテイクプロフィット、それらがトリガーされた場合に期待される損失と利益、そして、これらのレベルを手動で修正する機能なども実装できるといいかもしれません。ただ、後者は、厳密にアルゴリズム戦略に基づいてEAを開発するよりも、半自動売買のパネルを開発する場合の方が役に立つでしょう。一般的に、簡単な実装を始めるときでも、さらなるコード開発を想像しながら少し先に進むことができます。これは、すでに書かれたコードの修正に戻る可能性が低い方向性を選択するのに役立ちます。

そこで、チャート上で何かを可視化することに何らかの形で関連するすべてのオブジェクトのための新しい基本クラスを作成しましょう。

//+------------------------------------------------------------------+
//| Basic class for visualizing various objects                      |
//+------------------------------------------------------------------+
class CInterface {
protected:
   static ulong      s_magic;       // EA magic number
   bool              m_isActive;    // Is the interface active?
   bool              m_isChanged;   // Does the object have any changes?
public:
   CInterface();                    // Constructor
   virtual void      Redraw() = 0;  // Draw changed objects on the chart
   virtual void      Changed() {    // Set the flag for changes
      m_isChanged = true;
   }
};

ulong CInterface::s_magic = 0;

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CInterface::CInterface() :
   m_isActive(!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE)),
   m_isChanged(true) {}

このコードを現在のフォルダのInterface.mqhに保存します。

このクラスを基に2つの新しいクラスを作成します。

  • CVirtualChartOrder:端末のチャート上に1つの仮想ポジションまたは予約注文を表示するオブジェクト(グラフィカルな仮想ポジション)。チャートに変更があった場合、チャート上に仮想ポジションを描画することができ、必要な商品のチャートが端末で開かれていない場合は自動的に開きます。
  • CVirtualInterface:EAインタフェースのすべてのグラフィカルオブジェクトのアグリゲータ。今のところ、グラフィカルな仮想ポジションの配列だけが含まれています。仮想ポジションオブジェクトが作成されるたびに、グラフィカルな仮想ポジションオブジェクトが作成されます。また、仮想ポジションの構成の変更に関するメッセージを受信し、対応するグラフィカルな仮想ポジションを再描画させます。このようなアグリゲータは単一のインスタンス(シングルトンデザインパターンの実装)として存在し、CVirtualAdvisorクラスで利用可能です。

CVirtualChartOrderクラスは以下のようになります。

//+------------------------------------------------------------------+
//| Graphic virtual position class                                   |
//+------------------------------------------------------------------+
class CVirtualChartOrder : public CInterface {
   CVirtualOrder*    m_order;          // Associated virtual position (order)
   CChart            m_chart;          // Chart object to be displayed

   // Objects on the chart to display the virtual position
   CChartObjectHLine m_openLine;       // Open price line

   long              FindChart();      // Search/open the desired chart
public:
   CVirtualChartOrder(CVirtualOrder* p_order);     // Constructor
   ~CVirtualChartOrder();                          // Destructor

   bool              operator==(const ulong id) {  // Comparison operator by Id
      return m_order.Id() == id;
   }

   void              Show();    // Show a virtual position (order)
   void              Hide();    // Hide a virtual position (order) 

   virtual void      Redraw() override;   // Redraw a virtual position (order) 
};

Redraw()メソッドは、実行する必要があるかどうかを確認し、必要であれば、チャートから仮想ポジションを表示または非表示にするメソッドを呼び出します。

//+------------------------------------------------------------------+
//| Redraw a virtual position (order)                                |
//+------------------------------------------------------------------+
void CVirtualChartOrder::Redraw() {
   if(m_isChanged) {
      if(m_order.IsOpen()) {
         Show();
      } else {
         Hide();
      }
      m_isChanged = false;
   }
}

Show()表示メソッドでは、まずFindChart()メソッドを呼び出して、仮想ポジションをどのチャートに表示すべきかを決定します。このメソッドでは、単純に、一致する銘柄を持つチャートが見つかるまで、開いているすべてのチャートを繰り返し表示します。もし見つからなければ、新しいチャートを開きます。見つかった(あるいは開かれた)チャートはm_chartプロパティに格納されます。

//+------------------------------------------------------------------+
//| Finding a chart to display                                       |
//+------------------------------------------------------------------+
long CVirtualChartOrder::FindChart() {
   if(m_chart.ChartId() == -1 || m_chart.Symbol() != m_order.Symbol()) {
      long currChart, prevChart = ChartFirst();
      int i = 0, limit = 1000;

      currChart = prevChart;

      while(i < limit) { // we probably have no more than 1000 open charts
         if(ChartSymbol(currChart) == m_order.Symbol()) {
            return currChart;
         }
         currChart = ChartNext(prevChart); // get new chart on the basis of the previous one
         if(currChart < 0)
            break;        // end of chart list is reached
         prevChart = currChart; // memorize identifier of the current chart for ChartNext()
         i++;
      }

      // If a suitable chart is not found, then open a new one
      if(currChart == -1) {
         m_chart.Open(m_order.Symbol(), PERIOD_CURRENT);
      }
   }
   return m_chart.ChartId();
}

Show()メソッドは、始値に対応する水平線を1本引くだけです。その色とタイプは、ポジション(注文)の方向とタイプによって決定されます。Hide()メソッドはこの行を削除します。クラスはこれらのメソッドによって、さらに充実します。

取得したコードを現在のフォルダのVirtualChartOrder.mqhファイルに保存します。

CVirtualInterfaceクラスの実装は以下のようになります。

//+------------------------------------------------------------------+
//| EA GUI class                                                     |
//+------------------------------------------------------------------+
class CVirtualInterface : public CInterface {
protected:
// Static pointer to a single class instance
   static   CVirtualInterface *s_instance;

   CVirtualChartOrder *m_chartOrders[];   // Array of graphical virtual positions

//--- Private methods
   CVirtualInterface();   // Closed constructor

public:
   ~CVirtualInterface();  // Destructor

//--- Static methods
   static
   CVirtualInterface  *Instance(ulong p_magic = 0);   // Singleton - creating and getting a single instance

//--- Public methods
   void              Changed(CVirtualOrder *p_order); // Handle virtual position changes
   void              Add(CVirtualOrder *p_order);     // Add a virtual position

   virtual void      Redraw() override;   // Draw changed objects on the chart
};

Instance()を1つ作成する静的メソッドで、0以外のマジックナンバーが渡された場合に、s_magic静的変数を初期化するようにします。

//+------------------------------------------------------------------+
//| Singleton - creating and getting a single instance               |
//+------------------------------------------------------------------+
CVirtualInterface* CVirtualInterface::Instance(ulong p_magic = 0) {
   if(!s_instance) {
      s_instance = new CVirtualInterface();
   }
   if(s_magic == 0 && p_magic != 0) {
      s_magic = p_magic;
   }
   return s_instance;
}

仮想ポジションの開閉イベントを処理するメソッドでは、対応するグラフィカルな仮想ポジションオブジェクトを見つけ、そこに変更が発生したことをマークします。

//+------------------------------------------------------------------+
//| Handle virtual position changes                                  |
//+------------------------------------------------------------------+
void CVirtualInterface::Changed(CVirtualOrder *p_order) {
   // Remember that this position has changes
   int i;
   FIND(m_chartOrders, p_order.Id(), i);
   if(i != -1) {
      m_chartOrders[i].Changed();
      m_isChanged = true;
   }
}

最後に、Redraw()インターフェイスのレンダリングメソッドで、すべてのグラフィカル仮想ポジションをループで描画するメソッドを呼び出します。

//+------------------------------------------------------------------+
//| Draw changed objects on a chart                                  |
//+------------------------------------------------------------------+
void CVirtualInterface::Redraw() {
   if(m_isActive && m_isChanged) {  // If the interface is active and there are changes
      // Start redrawing graphical virtual positions 
      FOREACH(m_chartOrders, m_chartOrders[i].Redraw());
      m_isChanged = false;          // Reset the changes flag
   }
}

得られたコードを現在のフォルダのVirtualInterface.mqhファイルに保存します。

あとは、チャートに仮想ポジションを表示するサブシステムを機能させるための最終編集をするだけです。CVirtualAdvisorクラスに、新しいm_interfaceプロパティを追加します。ここには、ディスプレイインターフェイスオブジェクトの単一のインスタンスが格納されます。コンストラクタで初期化し、デストラクタで削除する必要があります。

//+------------------------------------------------------------------+
//| Class of the EA handling virtual positions (orders)              |
//+------------------------------------------------------------------+
class CVirtualAdvisor : public CAdvisor {
protected:
   ...
   CVirtualInterface *m_interface;     // Interface object to show the status to the user
   
   ...
};

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualAdvisor::CVirtualAdvisor(ulong p_magic = 1, string p_name = "") :
   ...
// Initialize the interface with the static interface
   m_interface(CVirtualInterface::Instance(p_magic)),
   ... {
   ...
};

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
void CVirtualAdvisor::~CVirtualAdvisor() {
   
... 
   delete m_interface;        // Remove the interface
}

OnTickイベントハンドラでは、すべての処理の後に、インターフェイスを再描画するメソッドの呼び出しを追加します。これは、ティック処理の最も重要でない部分であるためです。 

//+------------------------------------------------------------------+
//| OnTick event handler                                             |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Tick(void) {
// Receiver handles virtual positions
   m_receiver.Tick();

// Start handling in strategies
   CAdvisor::Tick();

// Adjusting market volumes
   m_receiver.Correct();

// Save status
   Save();
   
// Render the interface
   m_interface.Redraw();
}

CVirtualReceiverクラスでは,新しいm_interfaceプロパティも追加し,ディスプレイインターフェースオブジェクトのインスタンスを1つ格納します。コンストラクタで初期化する必要があります。

//+------------------------------------------------------------------+
//| Class for converting open volumes to market positions (receiver) |
//+------------------------------------------------------------------+
class CVirtualReceiver : public CReceiver {
protected:
   ...
   CVirtualInterface
   *m_interface;                          // Interface object to show the status to the user

   ...
};

//+------------------------------------------------------------------+
//| Closed constructor                                               |
//+------------------------------------------------------------------+
CVirtualReceiver::CVirtualReceiver() :
   m_interface(CVirtualInterface::Instance()),
   ... {}

必要な数の仮想ポジションを戦略に割り当てるメソッドでは、同時にそれらをインターフェイス追加します。

//+------------------------------------------------------------------+
//| Allocate the necessary amount of virtual positions to strategy   |
//+------------------------------------------------------------------+
static void CVirtualReceiver::Get(CVirtualStrategy *strategy,   // Strategy
                                  CVirtualOrder *&orders[],     // Array of strategy positions
                                  int n                         // Required number
                                 ) {
   CVirtualReceiver *self = Instance();   // Receiver singleton
   CVirtualInterface *draw = CVirtualInterface::Instance();
   ArrayResize(orders, n);                // Expand the array of virtual positions
   FOREACH(orders,
           orders[i] = new CVirtualOrder(strategy); // Fill the array with new objects
           APPEND(self.m_orders, orders[i]);
           draw.Add(orders[i])) // Register the created virtual position
   PrintFormat(__FUNCTION__ + " | OK, Strategy orders: %d from %d total",
               ArraySize(orders),
               ArraySize(self.m_orders));
}

最後にしなければならないのは、このクラスの仮想ポジションのオープン/クローズを処理するメソッドに、インターフェイスのアラートを追加することです。

void CVirtualReceiver::OnOpen(CVirtualOrder *p_order) {
   m_interface.Changed(p_order);
   ...
}

//+------------------------------------------------------------------+
//| Handle closing a virtual position                                |
//+------------------------------------------------------------------+
void CVirtualReceiver::OnClose(CVirtualOrder *p_order) {
   m_interface.Changed(p_order);
   ...
   }
}

VirtualAdvisor.mqhVirtualReceiver.mqhファイルに加えられた変更を現在のフォルダに保存します。

EAをコンパイルして実行すると、仮想ポジションや予約注文がある場合、このように表示されます。

図1:チャートでの仮想注文とポジションの表示

図1:チャートでの仮想注文とポジションの表示

ここで、点線は仮想予約注文を示し(オレンジ-売りストップ、青-買いストップ)、実線は仮想ポジションを示します(青-買い、赤-売り)。現時点では開始レベルしか表示されませんが、将来的にはより彩度の高い表示が可能になるでしょう。


美しいチャート

フォーラムでのディスカッションで、記事の読者(少なくともその一部)は、まず記事の最後を見て、そこで開発したEAをテストしたときの資金の伸びを示す美しいチャートを見ることを期待するという意見がありました。もし本当にそのようなチャートがあるのなら、記事の最初に戻って読む動機付けになります。

この記事を書いている間、使用したデモ戦略やデモEAの戦略インスタンスセットに変更は加えていません。すべては前回の記事掲載時と同じ状態です。したがって、ここには印象的なチャートはありません。

ただし、今回の記事と並行して、他の戦略を最適化し、訓練してきました。これらのテスト結果から、多数の戦略インスタンスを1つのEAに組み合わせることは有望な手法であると言えます。

様々な銘柄と時間枠で動作する戦略の約170インスタンスを使用しているEAのテスト実行の2つの例を示します。テスト期間は、2023-01-01から2024-02-23までです。この期間のデータは、最適化と訓練には使用されませんでした。資本管理の設定では、あるケースでは許容可能なドローダウンは約10%、別のケースでは約40%とするパラメータが設定されました。


図2:10%のドローダウンを許容するテスト結果


図3:40%のドローダウンを許容するテスト結果

このような結果が出たからといって、将来も同じことが繰り返される保証はありません。しかし、その可能性を高める努力はできます。この結果を悪化させないよう、また過剰訓練という自己欺瞞に陥らないよう努力するつもりです。


結論

この記事では、主要な方向性からやや離れた作業をしました。例えば、最近発表された記事「最適化アルゴリズムを使用してEAパラメータをオンザフライで設定する」で議論されているような、自動最適化に向かいたいと思います。

しかし、それにもかかわらず、ステータスを保存し、読み込む機能は、実際の口座で起動されるすべてのEAの重要なコンポーネントです。マーケットポジションだけでなく、多くの取引戦略で使用される予約注文を扱う能力も重要です。同時に、EA作業を可視化することで、開発段階での実装エラーを特定することができます。

ご清聴ありがとうございました。次回お会いしましょう。

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

MetaTraderのMultibot(第2回):動的テンプレートの改良 MetaTraderのMultibot(第2回):動的テンプレートの改良
前回の記事のテーマを発展させ、より柔軟で機能的なテンプレートを作成することにしました。このテンプレートは、より大きな機能を持ち、フリーランスとして、また外部ソリューションとの統合機能を備えた多通貨多期間EAを開発するためのベースとして効果的に使用することができます。
母集団最適化アルゴリズム:極値から抜け出す力(第II部) 母集団最適化アルゴリズム:極値から抜け出す力(第II部)
母集団の多様性が低いときに効率的に極小値を脱出して最大値に到達する能力という観点から、母集団最適化アルゴリズムの挙動を調べることを目的とした実験を続けます。研究結果が提供されます。
角度ベースの取引 角度ベースの取引
この記事では、角度ベースの取引について取り上げます。角度の組み立て方と、それを取引に利用する方法について見ていきます。
DoEasy - コントロール(第33部):垂直スクロールバー DoEasy - コントロール(第33部):垂直スクロールバー
この記事では、DoEasyライブラリのグラフィカル要素の開発を続け、フォームオブジェクトコントロールの垂直スクロールを追加し、さらに将来必要となる便利な関数やメソッドを紹介します。