English Русский 中文 Español Deutsch Português
MetaTrader 5での複数銘柄残高グラフ

MetaTrader 5での複数銘柄残高グラフ

MetaTrader 5 | 28 5月 2018, 10:18
1 477 0
Anatoli Kazharski
Anatoli Kazharski

内容

はじめに

前の記事のうちの1つでは、複数銘柄残高グラフの視覚化を検討しました。それ以来、多くのMQLライブラリが登場し、サードパーティのプログラムを使用せずにこのような視覚化をMetaTrader 5プラットフォームに完全に実装することができるようになりました。

本稿では、最後のテストの結果として、複数銘柄の残高グラフと預金損失率グラフを備えたグラフィカルインターフェイスを持つサンプルアプリケーションを示します。EAテストの完了後、取引履歴がファイルに書き込まれ、その後、これらのデータは読み取られてグラフに表示されることができます。

さらに、本稿ではEAのバージョンが示されています。このバージョンでは、取引中および視覚化モードでのテスト中に、複数銘柄の残高グラフがグラフィカルインターフェイス上で表示されて更新されます。


グラフィカルインターフェイスの開発

METATRADER 5における取引戦略最適化の可視化稿では、EasyAndFastライブラリをインクルードして使用する方法やMQLアプリケーション用のグラフィカルインターフェイスを開発する方法を詳しく調べました。したがって、ここでは適切なグラフィカルインターフェイスから始めます。 

グラフィカルインターフェイスで使用する要素を列挙しましょう。

  • コントロール用フォーム
  • グラフを最後のテストの結果で更新するためのボタン
  • 複数銘柄の残高グラフ
  • 預金損失率グラフ
  • 追加の要約情報を表示するためのステータスバー

以下のコードは、これらの要素を作成するためのメソッドの宣言を示しています。メソッドは別のインクルードファイルで実装されています。

//+------------------------------------------------------------------+
//| アプリケーション作成クラス                                          |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- ウィンドウ
   CWindow           m_window1;
   //--- ステータスバー
   CStatusBar        m_status_bar;
   //--- グラフ
   CGraph            m_graph1;
   CGraph            m_graph2;
   //--- ボタン
   CButton           m_update_graph;
   //---
public:
   //--- グラフィカルインターフェイスを作成する
   bool              CreateGUI(void);
   //---
private:
   //--- フォーム
   bool              CreateWindow(const string text);
   //--- ステータスバー
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- グラフ
   bool              CreateGraph1(const int x_gap,const int y_gap);
   bool              CreateGraph2(const int x_gap,const int y_gap);
   //--- ボタン
   bool              CreateUpdateGraph(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| コントロール要素作成メソッド                                        |
//+------------------------------------------------------------------+
#include "CreateGUI.mqh"
//+------------------------------------------------------------------+

この場合、グラフィカルインターフェイスを作成する主要メソッドは次のようになります。

//+------------------------------------------------------------------+
//| 最適化結果の分析とフレームの操作のための                              |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- コントロール要素のフォームを作成する
   if(!CreateWindow("Expert panel"))
      return(false);
//--- コントロール要素を作成する
   if(!CreateStatusBar(1,23))
      return(false);
   if(!CreateGraph1(1,50))
      return(false);
   if(!CreateGraph2(1,159))
      return(false);
   if(!CreateUpdateGraph(7,25,"Update data"))
      return(false);
//--- GUI 作成を完成する
   CWndEvents::CompletedGUI();
   return(true);
  }

その結果、EAをコンパイルしてターミナルにグラフをダウンロードすると、現在の結果は次のようになります。

 図1 EAグラフィカルインターフェイス

図1 EAグラフィカルインターフェイス

ここで、テスト後のファイルへのデータの書き込みについて考えましょう。


テスト用複数銘柄EA

テストには、標準パッケージに入っているMACD SampleEAを使って複数銘柄にします。このバージョンで使用されている複数銘柄構造は不正確です。パラメータが同じでも、テストが実行される銘柄(テスターの設定で選択された銘柄)によって結果が異なります。したがって、このEAは、本トピックの枠組みの中で得られた結果のテストと実証のみを目的としています。

複数銘柄EAを作成するための新しい可能性は、次のMetaTrader 5アップデートで紹介されます。そして、このタイプのEAのための最終的かつ普遍的バージョンの開発について考えることが可能になります。高速かつ正確な複数銘柄構造が緊急に必要な場合は、フォーラムで提案されているオプションをお試しください。

外部パラメータに対してテストが実行される銘柄を指定するためにもう1つ文字列パラメータを追加しましょう。

//--- 外部パラメータ
sinput string Symbols           ="EURUSD,USDJPY,GBPUSD,EURCHF"; // 銘柄
input  double InpLots           =0.1;                           // ロット
input  int    InpTakeProfit     =167;                           // 指値(ピップ単位)
input  int    InpTrailingStop   =97;                            // トレールストップ(ピップ単位)
input  int    InpMACDOpenLevel  =16;                            // MACDオープンレベル(ピップ単位)
input  int    InpMACDCloseLevel =19;                            // MACDクローズレベル(ピップ単位)
input  int    InpMATrendPeriod  =14;                            // MAトレンド期間

銘柄はコンマで区切られています。プログラムクラス( CProgram)は、このパラメータを読み込むメソッドと、市場で銘柄を確認して気配値表示に表示するメソッドを実装します。また、MQL5 クックブック:パラメータ数無制限での複数通貨対応 EXPERT 作成のように、事前に準備されたリストを使用して取引記号を指定することもできます。さらに、複数のリストを選択して選択することもできます。このような例は、記事MQL5 クックブック:オーバーフィットの影響低減とクオート不足への対処稿で提供されています。グラフィカルインターフェイスを使用して銘柄やそのリストを選択する方法をさらに増やすことができます。可能なオプションは後の記事のいずれかで示します。 

共通リストの文字をテストする前に、それらを配列に保存する必要があります。そして子の配列(source_array[])CProgram::CheckTradeSymbols()メソッドに渡します。ここで、最初のループでは、外部パラメータで指定された銘柄を渡します。2番目のループでは、この銘柄がブローカーサーバのリストにあるかどうかを確認します。ある場合は気配値表示に追加し、確認済み銘柄の配列にも追加します。 

銘柄が1つも検出されなかった場合は、EAが使用している現在の銘柄のみを使用します。

class CProgram : public CWndEvents
  {
private:
   //--- 渡された配列で取引銘柄を確認して使用可能な銘柄の配列を返す
   void              CheckTradeSymbols(string &source_array[],string &checked_array[]);
  };
//+------------------------------------------------------------------+
//| 渡された配列で取引銘柄を確認し                                       |
//| 使用可能な銘柄の配列を返す                                           |
//+------------------------------------------------------------------+
void CProgram::CheckTradeSymbols(string &source_array[],string &checked_array[])
  {
   int symbols_total     =::SymbolsTotal(false);
   int size_source_array =::ArraySize(source_array);
//--- 全体のリストで指定された銘柄を探す
   for(int i=0; i<size_source_array; i++)
     {
      for(int s=0; s<symbols_total; s++)
        {
         //--- 共通リストで現在の銘柄の名前を取得する
         string symbol_name=::SymbolName(s,false);
         //--- 一致した場合
         if(symbol_name==source_array[i])
           {
            //--- 気配値情報で銘柄を設定する
            ::SymbolSelect(symbol_name,true);
            //--- 確認された銘柄の配列に追加する
            int size_array=::ArraySize(checked_array);
            ::ArrayResize(checked_array,size_array+1);
            checked_array[size_array]=symbol_name;
            break;
           }
        }
     }
//--- 銘柄が1つも検出されなかった場合は、現在の銘柄のみを使用する
   if(::ArraySize(checked_array)<1)
     {
      ::ArrayResize(checked_array,1);
      checked_array[0]=_Symbol;
     }
  }

外部文字列を読み取るためにはCProgram::CheckSymbols()メソッドが使用されます。ここでは文字列は','を区切りとして配列に分割されます。結果の文字列の両側の隙間は切り取られます。その後、配列は上記で考察されたCProgram::CheckTradeSymbols()メソッドに検証のために送られます。

class CProgram : public CWndEvents
  {
private:
   //--- 文字列から取引するための銘柄を確認して配列に含める
   int               CheckSymbols(const string symbols_enum);
  };
//+-------------------------------------------------------------------------+
//| 文字列から取引するための銘柄を確認して配列に含める                            |
//+-------------------------------------------------------------------------+
int CProgram::CheckSymbols(const string symbols_enum)
  {
   if(symbols_enum!="")
      ::Print(__FUNCTION__," > input deal symbols: ",symbols_enum);
//--- 文字列から銘柄を取得する
   string symbols[];
   ushort u_sep=::StringGetCharacter(",",0);
   ::StringSplit(symbols_enum,u_sep,symbols);
//--- 両側のスペースを取り除く
   int elements_total=::ArraySize(symbols);
   for(int e=0; e<elements_total; e++)
     {
      ::StringTrimLeft(symbols[e]);
      ::StringTrimRight(symbols[e]);
     }
//--- 銘柄を確認する
   ::ArrayFree(m_symbols);
   CheckTradeSymbols(symbols,m_symbols);
//--- 取引銘柄数を取得する
   return(::ArraySize(m_symbols));
  }

取引戦略クラスを持つファイルがアプリケーションクラスのファイルに接続されます。CStrategy型の動的配列が作成されます。 

#include "Strategy.mqh"
//+------------------------------------------------------------------+
//| アプリケーション作成クラス                                          |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- 戦略配列
   CStrategy         m_strategy[];
  };

ここでは、プログラムの初期化中に外部パラメータから銘柄の配列とその番号を取得します。次に、戦略配列のサイズを銘柄数に設定し、すべての戦略インスタンスを初期化し、それぞれに銘柄名を渡します。

class CProgram : public CWndEvents
  {
private:
   //--- 銘柄の総数
   int               m_symbols_total;
  };
//+------------------------------------------------------------------+
//| 初期化                                                            |
//+------------------------------------------------------------------+
bool CProgram::OnInitEvent(void)
  {
//--- 取引銘柄を取得する
   m_symbols_total=CheckSymbols(Symbols);
//--- TS配列サイズ
   ::ArrayResize(m_strategy,m_symbols_total);
//--- 初期化
   for(int i=0; i<m_symbols_total; i++)
     {
      if(!m_strategy[i].OnInitEvent(m_symbols[i]))
         return(false);
     }
//--- 初期化が正常だった
   return(true);
  }

次に、ファイルへのデータの書き込みについて考えましょう。


ファイルへのデータの書き込み

最後のテストデータをターミナルの一般データフォルダに保存します。したがって、ファイルはMetaTrader 5プラットフォームからアクセスできます。コンストラクタでフォルダ名とファイル名を指定します

class CProgram : public CWndEvents
  {
private:
   //--- 最後のテスト結果を持つファイルへのパス
   string            m_last_test_report_path;
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_symbols_total(0)
  {
//--- 最後のテスト結果を持つファイルへのパス
   m_last_test_report_path=::MQLInfoString(MQL_PROGRAM_NAME)+"\\LastTest.csv";
  }

ファイルへの書き入れに使われるCProgram::CreateSymbolBalanceReport()メソッドについて考えてみましょう。このメソッドで作業するには(後でみる後一つに加えて)銘柄残高の配列が必要になります。

//--- 全ての銘柄の残高の配列
struct CReportBalance { double m_data[]; };
//+------------------------------------------------------------------+
//| アプリケーション作成クラス                                          |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- 全ての銘柄の残高の配列
   CReportBalance    m_symbol_balance[];
   //---
private:
   //--- 取引テストレポートをCSV形式で作成する
   void              CreateSymbolBalanceReport(void);
  };
//+------------------------------------------------------------------+
//| 取引テストレポートをCSV形式で作成する                                |
//+------------------------------------------------------------------+
void CProgram::CreateSymbolBalanceReport(void)
  {
   ...
  }

メソッドの初めには、ファイルを開いてターミナルの共有フォルダ( FILE_COMMON )で作業します。

...
//--- 汎用ターミナルフォルダにデータを書き込むためのファイルを作成する
   int file_handle=::FileOpen(m_last_test_report_path,FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON);
//--- ハンドルが有効な場合(ファイルが作成された/開かれた)
   if(file_handle==INVALID_HANDLE)
     {
      ::Print(__FUNCTION__," > Error creating file: ",::GetLastError());
      return;
     }
...

いくつかのレポートパラメータを形成するためには補助変数が必要です。取引の全履歴を以下のリストに記載されているデータとともにファイルに書きます。

  • 取引時刻
  • 銘柄
  • 種類
  • 方向
  • 数量
  • 価格
  • スワップ
  • 結果(損利)
  • 損失率
  • 残高(この列には合計残高が表示され、それに続くものにはテストで使用された銘柄の残高が含まれています。)

ここで、データヘッダで一列目を形成します

...
   double max_drawdown    =0.0; // 最大損失率
   double balance         =0.0; // 残高
   string delimeter       =","; // 区切り
   string string_to_write ="";  // エントリラインの形成
//--- ヘッダラインを形成する
   string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,PRICE,SWAP($),PROFIT($),DRAWDOWN(%),BALANCE";
...

複数銘柄が関与する場合はヘッダ行に名前を補足する必要があります。その後、ヘッダ(1番目の行) はファイルに書き入れられます。 

...
//--- 複数銘柄が関与する場合はヘッダ行を補足する
   int symbols_total=::ArraySize(m_symbols);
   if(symbols_total>1)
     {
      for(int s=0; s<symbols_total; s++)
         ::StringAdd(headers,delimeter+m_symbols[s]);
     }
//--- レポートヘッダを書く
   ::FileWrite(file_handle,headers);
...

次に、取引の全履歴とその数、配列サイズの設定を受け取ります。

...
//--- 全履歴を取得する
   ::HistorySelect(0,LONG_MAX);
//--- 取引数を見つける
   int deals_total=::HistoryDealsTotal();
//--- 銘柄の数でバランス配列の数を設定する
   ::ArrayResize(m_symbol_balance,symbols_total);
//--- 各銘柄の取引配列のサイズを設定する
   for(int s=0; s<symbols_total; s++)
      ::ArrayResize(m_symbol_balance[s].m_data,deals_total);
...

メインループでは、履歴全体を渡し、ファイルに書き込むための文字列を作成します。利益を計算するときは、スワップと手数料も考慮します。複数の銘柄がある場合は、2番目のループでそれらを処理し、銘柄ごとに残高を作成します。

データは文字列ごとにファイルに書き込まれます。ファイルはメソッドの最後に閉じられます。
...
//--- 反復してデータを書く
   for(int i=0; i<deals_total; i++)
     {
      //--- 取引チケットを取得する
      if(!m_deal_info.SelectByIndex(i))
         continue;
      //--- 価格の桁数を見つける
      int digits=(int)::SymbolInfoInteger(m_deal_info.Symbol(),SYMBOL_DIGITS);
      //--- 合計残高を計算する
      balance+=m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission();
      //--- つなげて書いて行を形成する
      ::StringConcatenate(string_to_write,
                          ::TimeToString(m_deal_info.Time(),TIME_DATE|TIME_MINUTES),delimeter,
                          m_deal_info.Symbol(),delimeter,
                          m_deal_info.TypeDescription(),delimeter,
                          m_deal_info.EntryDescription(),delimeter,
                          ::DoubleToString(m_deal_info.Volume(),2),delimeter,
                          ::DoubleToString(m_deal_info.Price(),digits),delimeter,
                          ::DoubleToString(m_deal_info.Swap(),2),delimeter,
                          ::DoubleToString(m_deal_info.Profit(),2),delimeter,
                          MaxDrawdownToString(i,balance,max_drawdown),delimeter,
                          ::DoubleToString(balance,2));
      //--- 銘柄が複数ある場合は残高を書く
      if(symbols_total>1)
        {
         //--- 銘柄を反復処理する
         for(int s=0; s<symbols_total; s++)
           {
            //--- 銘柄が一致して取引結果が0でない場合
            if(m_deal_info.Symbol()==m_symbols[s] && m_deal_info.Profit()!=0)
               //--- この銘柄の残高の取引を表示する。スワップと手数料を考慮する。
               m_symbol_balance[s].m_data[i]=m_symbol_balance[s].m_data[i-1]+m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission();
            //--- その他の場合は前の値を書く
            else
              {
               //--- 「預金残高」取引(最初の取引)の場合、残高はすべての銘柄で同じである
               if(m_deal_info.DealType()==DEAL_TYPE_BALANCE)
                  m_symbol_balance[s].m_data[i]=balance;
               //--- それ以外の場合は、前の値を現在のインデックスに書き込む
               else
                  m_symbol_balance[s].m_data[i]=m_symbol_balance[s].m_data[i-1];
              }
            //--- 銘柄残高を文字列に追加する
            ::StringAdd(string_to_write,delimeter+::DoubleToString(m_symbol_balance[s].m_data[i],2));
           }
        }
      //--- 形成された文字列を書く
      ::FileWrite(file_handle,string_to_write);
      //--- 次の文字列の変数を強制的にゼロに設定する
      string_to_write="";
     }
//--- ファイルを閉じる
   ::FileClose(file_handle);
...

文字列を作成するときはCProgram::MaxDrawdownToString() メソッドを使用して、合計残高を計算するためのファイルに書き込みます(下のコードを参照)。最初の呼び出しでは、損失率はゼロに等しくなります。現在の残高は局地的最大/最小として保存されます。次のメソッド呼び出しの際、損失率が以前の値で計算され、残高が保存された値を超えると局地的最大値が更新されます。それ以外の場合は、局地的最小値が更新され、ゼロ値(空文字列)が返されます。

class CProgram : public CWndEvents
  {
private:
   //--- 局地的最大値から最大損失率を取得する
   string            MaxDrawdownToString(const int deal_number,const double balance,double &max_drawdown);
  };
//+------------------------------------------------------------------+
//| 局地的最大値から最大損失率を取得する                                 |
//+------------------------------------------------------------------+
string CProgram::MaxDrawdownToString(const int deal_number,const double balance,double &max_drawdown)
  {
//--- レポートで表示するための文字列
   string str="";
//--- 局地的最大値と損失率の計算
   static double max=0.0;
   static double min=0.0;
//--- 初めての取引の場合
   if(deal_number==0)
     {
      //--- まだ損失がない
      max_drawdown=0.0;
      //--- 初期のポイントを局地的最大値として設定する
      max=balance;
      min=balance;
     }
   else
     {
      //--- 現在の残高が保存されたものを上回る場合
      if(balance>max)
        {
         //--- 前の値で損失率を計算する
         max_drawdown=100-((min/max)*100);
         //--- 局地的最大値を更新する
         max=balance;
         min=balance;
        }
      else
        {
         //--- 0損失率を取得して最小値を更新する
         max_drawdown=0.0;
         min=fmin(min,balance);
        }
     }
//--- レポートのための文字列を定義する
   str=(max_drawdown==0)?"" : ::DoubleToString(max_drawdown,2);
   return(str);
  }

このファイル構造はExcelで開くことができます(下記スクリーンショット参照)。

 図2 レポートファイル構造

図2 Excelでのレポートファイル構造

その結果、テストレポートを準備するためのCProgram::CreateSymbolBalanceReport() メソッドがテストの最後で呼び出されます。

//+------------------------------------------------------------------+
//| テスト完了イベント                                                 |
//+------------------------------------------------------------------+
double CProgram::OnTesterEvent(void)
  {
//--- レポートはテスト後のみに書く
   if(::MQLInfoInteger(MQL_TESTER) && !::MQLInfoInteger(MQL_OPTIMIZATION) && 
      !::MQLInfoInteger(MQL_VISUAL_MODE) && !::MQLInfoInteger(MQL_FRAME_MODE))
     {
      //--- レポートを形成してファイルに書く
      CreateSymbolBalanceReport();
     }
//---
   return(0.0);
  }

ここで、レポートデータの読み込みについて考えましょう。


ファイルからのデータの抽出

上記がすべて実装されると、各EAのストラテジーテスターでの確認はレポートをファイルに書き込むことで終了します。次に、レポートからデータを読み取るために使用されるメソッドについて考えてみましょう。まず、ファイルを読み込み、作業を便利にするためにその内容を配列に挿入する必要があります。これを実現するために CProgram::ReadFileToArray() メソッドを使用します。ここでは、EAテスト終了時の取引履歴が書かれたファイルを開きます。ループの最後の文字列までファイルを読み込み、ソースデータで配列を埋めます。 

class CProgram : public CWndEvents
  {
private:
   //--- ファイルからのデータのための配列
   string            m_source_data[];
   //--- 
private:
   //--- ファイルを渡された配列に読み込む
   bool              ReadFileToArray(const int file_handle);
  };
//+------------------------------------------------------------------+
//| ファイルを渡された配列に読み込む                                     |
//+------------------------------------------------------------------+
bool CProgram::ReadFileToArray(const int file_handle)
  {
//--- ファイルを開く
   int file_handle=::FileOpen(m_last_test_report_path,FILE_READ|FILE_ANSI|FILE_COMMON);
//--- ファイルが開かれていない場合は終了する
   if(file_handle==INVALID_HANDLE)
      return(false);
//--- 配列を開放する
   ::ArrayFree(m_source_data);
//--- ファイルを配列に読み込む
   while(!::FileIsEnding(file_handle))
     {
      int size=::ArraySize(m_source_data);
      ::ArrayResize(m_source_data,size+1,RESERVE);
      m_source_data[size]=::FileReadString(file_handle);
     }
//--- ファイルを閉じる
   ::FileClose(file_handle);
   return(true);
  }

BALANCE列のインデックスを定義するには補助的なCProgram::GetBalanceIndex()メソッドが必要です。',' 区切り文字を使った文字列分割の要素の動的配列とヘッダ文字列を引数としてを渡すことができます。この文字列では、列名の検索が実行されます。  

class CProgram : public CWndEvents
  {
private:
   //--- レポートの初期バランスインデックス
   bool              GetBalanceIndex(const string headers);
  };
//+------------------------------------------------------------------+
//| データのコピーを開始するインデックスを定義する                         |
//+------------------------------------------------------------------+
bool CProgram::GetBalanceIndex(const string headers)
  {
//--- 区切り文字で文字列要素を取得する
   string str_elements[];
   ushort u_sep=::StringGetCharacter(",",0);
   ::StringSplit(headers,u_sep,str_elements);
//--- 'BALANCE' 列を検索する
   int elements_total=::ArraySize(str_elements);
   for(int e=elements_total-1; e>=0; e--)
     {
      string str=str_elements[e];
      ::StringToUpper(str);
      //--- 必要なヘッダを持つ列が見つかった場合
      if(str=="BALANCE")
        {
         m_balance_index=e;
         break;
        }
     }
//--- 'BALANCE'列が見つからない場合はメッセージを表示する
   if(m_balance_index==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > In the report file, there is no heading \'BALANCE\' !");
      return(false);
     }
//--- 成功
   return(true);
  }

取引番号は両方のグラフでX軸で表示されます。日付範囲は余分な情報として残高グラフのフッタに表示されます。CProgram::GetDateRange()メソッドは、取引履歴の開始日と終了日を定義するために実装されています。2つの文字列変数は参照よって取引履歴の開始日と終了日に渡されます。

class CProgram : public CWndEvents
  {
private:
   //--- 日付の範囲
   void              GetDateRange(string &from_date,string &to_date);
  };
//+------------------------------------------------------------------+
//| テスト範囲の開始日と終了日を取得する                                  |
//+------------------------------------------------------------------+
void CProgram::GetDateRange(string &from_date,string &to_date)
  {
//--- 文字列が2つ以下の場合は終了する
   int strings_total=::ArraySize(m_source_data);
   if(strings_total<3)
      return;
//--- レポートの開始及び終了日を取得する
   string str_elements[];
   ushort u_sep=::StringGetCharacter(",",0);
//---
   ::StringSplit(m_source_data[1],u_sep,str_elements);
   from_date=str_elements[0];
   ::StringSplit(m_source_data[strings_total-1],u_sep,str_elements);
   to_date=str_elements[0];
  }

残高および損失率のデータを取得するにはCProgram::GetReportDataToArray()及びCProgram::AddDrawDown()メソッドが使用されます。後者は前者で呼び出され、そのコードは非常に短いです(下記のコードを参照)。ここには取引インデックスと損失率の値が渡されます。インデックスと値は適切な配列に挿入され、その値はグラフに表示されます。値はm_dd_y[]に保存され、この値を表示するインデックスはm_dd_x[]に保存されます。したがって、値のないインデックスに基づくグラフは何も表示しません(空の値)。

class CProgram : public CWndEvents
  {
private:
   //--- 合計残高による損失率
   double            m_dd_x[];
   double            m_dd_y[];
   //--- 
private:
   //--- 損失率を配列に追加する 
   void              AddDrawDown(const int index,const double drawdown);
  };
//+------------------------------------------------------------------+
//| 損失率を配列に追加する                                              |
//+------------------------------------------------------------------+
void CProgram::AddDrawDown(const int index,const double drawdown)
  {
   int size=::ArraySize(m_dd_y);
   ::ArrayResize(m_dd_y,size+1,RESERVE);
   ::ArrayResize(m_dd_x,size+1,RESERVE);
   m_dd_y[size] =drawdown;
   m_dd_x[size] =(double)index;
  }

残高グラフの配列サイズと系列の数は、最初にCProgram::GetReportDataToArray()メソッドで定義されます。その後に ヘッダ配列を初期化します。その後、区切りによる文字列要素は反復して文字列ごとに取り出され、データが損失率及び残高配列に配置されます。  

class CProgram : public CWndEvents
  {
private:
   //--- レポートから銘柄データを取得する
   int               GetReportDataToArray(string &headers[]);
  };
//+------------------------------------------------------------------+
//| レポートから銘柄データを取得する                                     |
//+------------------------------------------------------------------+
int CProgram::GetReportDataToArray(string &headers[])
  {
//--- ヘッダ文字列要素を取得する
   string str_elements[];
   ushort u_sep=::StringGetCharacter(",",0);
   ::StringSplit(m_source_data[0],u_sep,str_elements);
//--- 配列サイズ
   int strings_total  =::ArraySize(m_source_data);
   int elements_total =::ArraySize(str_elements);
//--- 配列を開放する
   ::ArrayFree(m_dd_y);
   ::ArrayFree(m_dd_x);
//--- シリーズの数を取得する
   int curves_total=elements_total-m_balance_index;
   curves_total=(curves_total<3)?1 : curves_total;
//--- 配列のサイズをシリーズ数に設定する
   ::ArrayResize(headers,curves_total);
   ::ArrayResize(m_symbol_balance,curves_total);
//--- シリーズの数を設定する
   for(int i=0; i<curves_total; i++)
      ::ArrayResize(m_symbol_balance[i].m_data,strings_total,RESERVE);
//--- 複数の銘柄がある場合(ヘッダを受け取る)
   if(curves_total>2)
     {
      for(int i=0,e=m_balance_index; e<elements_total; e++,i++)
         headers[i]=str_elements[e];
     }
   else
      headers[0]=str_elements[m_balance_index];
//--- データを取得する
   for(int i=1; i<strings_total; i++)
     {
      ::StringSplit(m_source_data[i],u_sep,str_elements);
      //--- データを配列に集める
      if(str_elements[m_balance_index-1]!="")
         AddDrawDown(i,double(str_elements[m_balance_index-1]));
      //--- 複数の銘柄がある場合
      if(curves_total>2)
         for(int b=0,e=m_balance_index; e<elements_total; e++,b++)
            m_symbol_balance[b].m_data[i]=double(str_elements[e]);
      else
         m_symbol_balance[0].m_data[i]=double(str_elements[m_balance_index]);
     }
//--- 1番目のシリーズの値
   for(int i=0; i<curves_total; i++)
      m_symbol_balance[i].m_data[0]=(strings_total<2)?0 : m_symbol_balance[i].m_data[1];
//--- シリーズの数を取得する
   return(curves_total);
  }

次に、取得したデータをグラフに表示する方法を考察します。


グラフでのデータ表示

前のセクションで考察した補助的メソッドは残高グラフを更新するCProgram::UpdateBalanceGraph()メソッドの初めで呼び出されます。最後のテストに参加している銘柄の数が変わる可能性があるので、現在のシリーズがグラフから削除されます。次に、CProgram::GetReportDataToArray()メソッドで定義されている現在の銘柄数で反復して新しい残高データ系列を追加し、最小値と最大値をY軸で定義します。 

ここでは、クラスフィールドにシリーズのサイズとX軸によるスケール間隔も記憶します。これらの値は、損失率グラフの書式設定にも必要です。5%に等しいグラフの極点のインデントがY軸に対して計算されます。結果として、これらの値はすべて残高グラフに適用され、グラフは最近の変更を表示するために更新されます。 

class CProgram : public CWndEvents
  {
private:
   //--- シリーズでの全部のデータ
   double            m_data_total;
   //--- Xスケールの間隔を調整する
   double            m_default_step;
   //--- 
private:
   //--- 残高グラフでデータを更新する
   void              UpdateBalanceGraph(void);
  };
//+------------------------------------------------------------------+
//| 残高グラフを更新する                                                |
//+------------------------------------------------------------------+
void CProgram::UpdateBalanceGraph(void)
  {
//--- テストの範囲を取得する
   string from_date=NULL,to_date=NULL;
   GetDateRange(from_date,to_date);
//--- データのコピーを開始するインデックスを定義する
   if(!GetBalanceIndex(m_source_data[0]))
      return;
//--- レポートから銘柄データを取得する
   string headers[];
   int curves_total=GetReportDataToArray(headers);

//--- 新しいデータを使用してすべてのグラフのシリーズを更新する
   CColorGenerator m_generator;
   CGraphic *graph=m_graph1.GetGraphicPointer();
//--- グラフを削除する
   int total=graph.CurvesTotal();
   for(int i=total-1; i>=0; i--)
      graph.CurveRemoveByIndex(i);
//--- チャート高/低
   double y_max=0.0,y_min=m_symbol_balance[0].m_data[0];
//--- データを追加する
   for(int i=0; i<curves_total; i++)
     {
      //--- Y軸で高/低を定義する
      y_max=::fmax(y_max,m_symbol_balance[i].m_data[::ArrayMaximum(m_symbol_balance[i].m_data)]);
      y_min=::fmin(y_min,m_symbol_balance[i].m_data[::ArrayMinimum(m_symbol_balance[i].m_data)]);
      //--- グラフにシリーズを追加する
      CCurve *curve=graph.CurveAdd(m_symbol_balance[i].m_data,m_generator.Next(),CURVE_LINES,headers[i]);
     }
//--- 値の数とX軸グリッドステップ
   m_data_total   =::ArraySize(m_symbol_balance[0].m_data)-1;
   m_default_step =(m_data_total<10)?1 : ::MathFloor(m_data_total/5.0);
//--- 範囲とインデント
   double range  =::fabs(y_max-y_min);
   double offset =range*0.05;
//--- 1番目のシリーズの色
   graph.CurveGetByIndex(0).Color(::ColorToARGB(clrCornflowerBlue));
//--- x軸プロパティ
   CAxis *x_axis=graph.XAxis();
   x_axis.AutoScale(false);
   x_axis.Min(0);
   x_axis.Max(m_data_total);
   x_axis.MaxGrace(0);
   x_axis.MinGrace(0);
   x_axis.DefaultStep(m_default_step);
   x_axis.Name(from_date+" - "+to_date);
//--- Y軸プロパティ
   CAxis *y_axis=graph.YAxis();
   y_axis.AutoScale(false);
   y_axis.Min(y_min-offset);
   y_axis.Max(y_max+offset);
   y_axis.MaxGrace(0);
   y_axis.MinGrace(0);
   y_axis.DefaultStep(range/10.0);
//--- グラフを更新する
   graph.CurvePlotAll();
   graph.Update();
  }

損失率グラフを更新するにはCProgram::UpdateDrawdownGraph()メソッドが使用されます。データは CProgram::UpdateBalanceGraph()メソッドで既に計算されているため、グラフに適用して更新するだけです。

class CProgram : public CWndEvents
  {
private:
   //--- 損失率グラフのデータを更新する
   void              UpdateDrawdownGraph(void);
  };
//+------------------------------------------------------------------+
//| 損失率グラフを更新する                                              |
//+------------------------------------------------------------------+
void CProgram::UpdateDrawdownGraph(void)
  {
//--- 損失率グラフを更新する
   CGraphic *graph=m_graph2.GetGraphicPointer();
   CCurve *curve=graph.CurveGetByIndex(0);
   curve.Update(m_dd_x,m_dd_y);
   curve.PointsFill(false);
   curve.PointsSize(6);
   curve.PointsType(POINT_CIRCLE);
//--- x軸プロパティ
   CAxis *x_axis=graph.XAxis();
   x_axis.AutoScale(false);
   x_axis.Min(0);
   x_axis.Max(m_data_total);
   x_axis.MaxGrace(0);
   x_axis.MinGrace(0);
   x_axis.DefaultStep(m_default_step);
//--- グラフを更新する
   graph.CalculateMaxMinValues();
   graph.CurvePlotAll();
   graph.Update();
  }

CProgram::UpdateBalanceGraph()及びCProgram::UpdateDrawdownGraph()メソッドはCProgram::UpdateGraphs()メソッドで呼び出されます。その前にCProgram::ReadFileToArray()メソッドが初めに呼び出されます。これは、EAの最後のテスト結果を持つファイルからデータを受信します。 

class CProgram : public CWndEvents
  {
private:
   //--- 最後のテスト結果のグラフのデータを更新する
   void              UpdateGraphs(void);
  };
//+------------------------------------------------------------------+
//| グラフを更新する                                                   |
//+------------------------------------------------------------------+
void CProgram::UpdateGraphs(void)
  {
//--- ファイルからのデータを配列に書き入れる
   if(!ReadFileToArray())
     {
      ::Print(__FUNCTION__," > Could not open the test results file!");
      return;
     }
//---残高及び損失率グラフをリフレッシュする
   UpdateBalanceGraph();
   UpdateDrawdownGraph();
  }

結果の表示

最後のテストの結果をインターフェイスグラフに表示するには、1つのボタンをクリックします。該当するイベントは CProgram::OnEvent()メソッドで処理されます。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタンクリックイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- 'Update data'の押下
      if(lparam==m_update_graph.Id())
        {
         //--- グラフを更新する
         UpdateGraphs();
         return;
        }
      //---
      return;
     }
  }

ボタンをクリックする前にEAが既にテストされている場合は、次が表示されます。

図3 EAの最後のテスト結果 

図3 EAの最後のテスト結果

したがって、EAがグラフにアップロードされている場合は、パラメータ最適化後に複数のテスト結果を表示しながら、複数銘柄の残高グラフに変更がすぐに表示されます。 

取引及びテスト中の複数銘柄残高グラフ

さて、複数銘柄の残高グラフが取引中に表示され、更新される第2のEAバージョンを考えてみましょう。 

グラフィカルインターフェイスは、上記のバージョンとほぼ同じです。唯一の違いは、更新ボタンがドロップダウンカレンダーに置き換えられ、取引結果がグラフに表示される日付を指定できることです。

OnTrade()メソッドにイベントが到着したら、履歴の変更を確認します。CProgram::IsLastDealTicket()メソッドは、新しい取引が履歴に確実に追加されるようにするために使用されます。このメソッドでは、最後の呼び出し後にメモリに保存された時刻から履歴を取得します。次に、最終取引チケットとメモリに保存されたチケットを確認します。チケットが異なる場合は、次の確認のために保存されたチケットと最終取引時刻を更新 し、履歴が変更されたことを知らせる 'true'プロパティを取得します。

class CProgram : public CWndEvents
  {
private:
   //--- 最後に変更された取引の時刻とチケット
   datetime          m_last_deal_time;
   ulong             m_last_deal_ticket;
   //--- 
private:
   //--- 新しい取引を確認する
   bool              IsLastDealTicket(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deal_time(NULL),
                           m_last_deal_ticket(WRONG_VALUE)
  {
  }
//+------------------------------------------------------------------+
//| 指定された銘柄の最終取引イベントを取得する                            |
//+------------------------------------------------------------------+
bool CProgram::IsLastDealTicket(void)
  {
//--- イベントがまだ受け取られていない場合は終了する
   if(!::HistorySelect(m_last_deal_time,LONG_MAX))
      return(false);
//--- 取得したリストでの取引数を取得する
   int total_deals=::HistoryDealsTotal();
//--- 受信したリストのすべての取引を最後から最初へ処理する
   for(int i=total_deals-1; i>=0; i--)
     {
      //--- 取引チケットを取得する
      ulong deal_ticket=::HistoryDealGetTicket(i);
      //--- チケットが等しい場合は終了する
      if(deal_ticket==m_last_deal_ticket)
         return(false);
      //--- チケットが異なる場合は、そう通知する
      else
        {
         datetime deal_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME);
         //--- 最終取引時刻とチケットを保存する
         m_last_deal_time   =deal_time;
         m_last_deal_ticket =deal_ticket;
         return(true);
        }
     }
//--- あと1つの銘柄のチケット
   return(false);
  }

取引履歴を見て配列にデータを書き込む前に、履歴にある銘柄と配列のサイズを設定する数を定義する必要があります。これにはCProgram::GetHistorySymbols()メソッドを使用します。それを呼び出す前に、履歴を希望範囲で選択します。次に、履歴にある銘柄を文字列に追加します。銘柄が繰り返されないようにするには、指定された部分文字列を確認します。その後、履歴で検出された銘柄を配列に追加 して、銘柄数を取得します。

class CProgram : public CWndEvents
  {
private:
   //--- 履歴の銘柄配列
   string            m_symbols_name[];
   //--- 
private:
   //--- 口座履歴から銘柄を取得して数を返す
   int               GetHistorySymbols(void);
  };
//+------------------------------------------------------------------+
//| 口座履歴から銘柄を取得して数を返す                                   |
//+------------------------------------------------------------------+
int CProgram::GetHistorySymbols(void)
  {
   string check_symbols="";
//--- 初めに反復処理で取引された銘柄を取得する
   int deals_total=::HistoryDealsTotal();
   for(int i=0; i<deals_total; i++)
     {
      //--- 取引チケットを取得する
      if(!m_deal_info.SelectByIndex(i))
         continue;
      //--- 銘柄名がある場合
      if(m_deal_info.Symbol()=="")
         continue;
      //--- そのような文字列がない場合は追加する
      if(::StringFind(check_symbols,m_deal_info.Symbol(),0)==-1)
         ::StringAdd(check_symbols,(check_symbols=="")?m_deal_info.Symbol() : ","+m_deal_info.Symbol());
     }
//--- 栗木文字で文字列要素を取得する
   ushort u_sep=::StringGetCharacter(",",0);
   int symbols_total=::StringSplit(check_symbols,u_sep,m_symbols_name);
//--- 銘柄数を返す
   return(symbols_total);
  }

複数銘柄残高を取得するには CProgram::GetHistorySymbolsBalance() メソッドを呼び出します。

class CProgram : public CWndEvents
  {
private:
   //--- 合計残高と銘柄ごとの残高を別々に取得する
   void              GetHistorySymbolsBalance(void);
  };
//+------------------------------------------------------------------+
//| 合計残高と銘柄ごとの残高を別々に取得する                              |
//+------------------------------------------------------------------+
void CProgram::GetHistorySymbolsBalance(void)
  {
   ...
  }

ここでは、当初の口座残高を取得する必要があります。最初の取引の履歴を取得します。初期残高として使用されます。取引結果が表示されるカレンダーの日付を指定することは可能だとみなされます。したがって、履歴を再度選択します。その後CProgram::GetHistorySymbols()メソッドを使用して、選択した履歴での銘柄とその番号を取得します。その後、配列のサイズを設定します。履歴結果の範囲を表示するための開始日と終了日を定義します。 

...
//--- 初期入金
   ::HistorySelect(0,LONG_MAX);
   double balance=(m_deal_info.SelectByIndex(0))?m_deal_info.Profit() : 0;
//--- 指定された日付の履歴を取得する
   ::HistorySelect(m_from_trade.SelectedDate(),LONG_MAX);
//--- 銘柄数を取得する
   int symbols_total=GetHistorySymbols();
//--- 配列を開放する
   ::ArrayFree(m_dd_x);
   ::ArrayFree(m_dd_y);
//--- 残高配列のサイズを銘柄の数+1(合計残高)に設定する
   ::ArrayResize(m_symbols_balance,(symbols_total>1)?symbols_total+1 : 1);
//--- 銘柄ごろに取引配列のサイズを設定する
   int deals_total=::HistoryDealsTotal();
   for(int s=0; s<=symbols_total; s++)
     {
      if(symbols_total<2 && s>0)
         break;
      //---
      ::ArrayResize(m_symbols_balance[s].m_data,deals_total);
      ::ArrayInitialize(m_symbols_balance[s].m_data,0);
     }
//--- 残高かーブの数
   int balances_total=::ArraySize(m_symbols_balance);
//--- 履歴の始めと終わり
   m_begin_date =(m_deal_info.SelectByIndex(0))?m_deal_info.Time() : m_from_trade.SelectedDate();
   m_end_date   =(m_deal_info.SelectByIndex(deals_total-1))?m_deal_info.Time() : ::TimeCurrent();
...

銘柄と損失率残高は次のループで計算されます。得られたデータは配列に配置されます。前のセクションで説明したメソッドは、ここで損失率を計算するためにも使用されます

...
//--- 最大損失率
   double max_drawdown=0.0;
//--- 残高配列を渡された配列に書く
   for(int i=0; i<deals_total; i++)
     {
      //--- 取引配列を取得する
      if(!m_deal_info.SelectByIndex(i))
         continue;
      //--- 1番目の取引で初期化する
      if(i==0 && m_deal_info.DealType()==DEAL_TYPE_BALANCE)
         balance=0;
      //--- 指定された日から
      if(m_deal_info.Time()>=m_from_trade.SelectedDate())
        {
         //--- 合計残高を数える
         balance+=m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission();
         m_symbols_balance[0].m_data[i]=balance;
         //--- 損失率を計算する
         if(MaxDrawdownToString(i,balance,max_drawdown)!="")
            AddDrawDown(i,max_drawdown);
        }
      //--- 複数銘柄が使用されている場合、銘柄の残高値を記述する
      if(symbols_total<2)
         continue;
      //--- 指定された日からのみ
      if(m_deal_info.Time()<m_from_trade.SelectedDate())
         continue;
      //--- すべての銘柄をみる
      for(int s=1; s<balances_total; s++)
        {
         int prev_i=i-1;
         //--- 「預金残高」取引(最初の取引)の場合
         if(prev_i<0 || m_deal_info.DealType()==DEAL_TYPE_BALANCE)
           {
            //--- ... 残高はすべての銘柄で同じである
            m_symbols_balance[s].m_data[i]=balance;
            continue;
           }
         //--- 銘柄が等しく取引結果がゼロでない場合
         if(m_deal_info.Symbol()==m_symbols_name[s-1] && m_deal_info.Profit()!=0)
           {
            //--- この銘柄の残高で取引を反映するスワップと手数料を考慮する。
            m_symbols_balance[s].m_data[i]=m_symbols_balance[s].m_data[prev_i]+m_deal_info.Profit()+m_deal_info.Swap()+m_deal_info.Commission();
           }
         //--- その他の場合は前の値を書く
         else
            m_symbols_balance[s].m_data[i]=m_symbols_balance[s].m_data[prev_i];
        }
     }
...

データはグラフに追加されCProgram::UpdateBalanceGraph()及びCProgram::UpdateDrawdownGraph()メソッドを使用して更新されます。コードは、前のセクションで考察した最初のEAバージョンのコードとほぼ同じです。したがって、すぐに呼び出します。

まず、これらのメソッドはグラフィカルインターフェイスを作成するときに呼び出されるので、取引結果はすぐに表示されます。その後、 OnTrade()メソッドで取引イベントを受け取るとグラフが更新されます。 

class CProgram : public CWndEvents
  {
private:
   //--- グラフを初期化する
   void              UpdateBalanceGraph(const bool update=false);
   void              UpdateDrawdownGraph(void);
  };
//+------------------------------------------------------------------+
//| 取引操作イベント                                                   |
//+------------------------------------------------------------------+
void CProgram::OnTradeEvent(void)
  {
//--- 残高及び損失率グラフを更新する
   UpdateBalanceGraph();
   UpdateDrawdownGraph();
  }

さらに、グラフィカルインターフェイスでは、残高グラフを作成する日付を指定できます。最終取引チケットを確認せずにグラフを強制的に更新するにはCProgram::UpdateBalanceGraph()メソッドにtrueを渡します。

カレンダーの日付を変更するイベント(ON_CHANGE_DATE)は、次のように処理されます。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- カレンダーの日付を選択するイベント
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      if(lparam==m_from_trade.Id())
        {
         UpdateBalanceGraph(true);
         UpdateDrawdownGraph();
         m_from_trade.ChangeComboBoxCalendarState();
        }
      //---
      return;
     }
  }

以下では、テスターの視覚化モードでこれがどのように動作するかを見ることができます。

図4 視覚化モードでのテスター結果の表示

図4 視覚化モードでのテスター結果の表示

シグナルサービスからのレポートの視覚化

ユーザにとって便利な補足として、シグナルサービスでレポートから取引結果を視覚化できるEAを作成します。

必要なシグナルのページに移動して取引履歴を選択します。


図5 シグナル取引履歴

取引履歴のCSVファイルをダウンロードするためのリンクは、リストの下にあります。

 図6 取引履歴のCSVファイルへのエクスポート

図6 取引履歴のCSVファイルへのエクスポート

これらの、現在のEAを実装するファイルは\MQL5\Filesに配置する必要があります。EAに外部パラメータを1つ追加します。レポートファイルの名前がグラフ上に表示されます。

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- 外部パラメータ
input string PathToFile=""; // ファイルへのパス
...

図7 レポートファイルを指定するための外部パラメータ

図7 レポートファイルを指定するための外部パラメータ

このEAバージョンのグラフィカルインターフェイスには2つのグラフしか含まれていません。EAはターミナルチャートで起動されると、設定で指定されたファイルを開こうとします。そのようなファイルが見つからない場合、プログラムは操作ログにメッセージを表示します。ここでのメソッドのセットは、上記のバージョンとほぼ同じです。若干の違いは存在しますが、主な原則は同じです。アプローチが大幅に変更されたメソッドだけを考えてみましょう。

ファイルが読み込まれ、そのファイルからの文字列がソースデータの配列に配置されたとします。今度は、このデータを表内で行われるように、2次元配列に分散する必要があります。これは、便利なように取引開始時刻が一番古いものから新しいものまでにデータを並び替えるために必要です。これには別の配列の配列が必要です。 

//--- ファイルからのデータのための配列
struct CReportTable
  {
   string            m_rows[];
  };
//+------------------------------------------------------------------+
//| アプリケーション作成クラス                                          |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- レポートのための表
   CReportTable      m_columns[];
   //--- 文字列と列の数
   uint              m_rows_total;
   uint              m_columns_total;
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_rows_total(0),
                           m_columns_total(0)
  {
...
  }

配列の配列をソートするには、以下のメソッドが必要です。

class CProgram : public CWndEvents
  {
private:
   //--- 高速ソートメソッド
   void              QuickSort(uint beg,uint end,uint column);
   //--- ソート条件を確認する
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
   //--- 指定されたセルの値をスワップする
   void              Swap(uint r1,uint r2);
  };

これらのメソッドはすべて 前の記事の1つで徹底的に議論されました。 

すべての基本的な操作はCProgram::GetData()メソッドで行われます。もっと詳しく説明しましょう。 

class CProgram : public CWndEvents
  {
private:
   //--- データを配列に取得する
   int               GetData(void);
  };
//+------------------------------------------------------------------+
//| レポートから銘柄データを取得する                                     |
//+------------------------------------------------------------------+
int CProgram::GetData(void)
  {
...
  }

まず、文字列と文字列要素の数を ';'区切り文字で定義しましょう。次に、レポートに表示される銘柄名とその番号を別の配列に取得します。その後、配列を準備し、それらにレポートデータを入力します。

...
//--- ヘッダ文字列要素を取得する
   string str_elements[];
   ushort u_sep=::StringGetCharacter(";",0);
   ::StringSplit(m_source_data[0],u_sep,str_elements);
//--- 文字列と文字列要素の数
   int strings_total  =::ArraySize(m_source_data);
   int elements_total =::ArraySize(str_elements);
//--- 銘柄を取得する
   if((m_symbols_total=GetHistorySymbols())==WRONG_VALUE)
     return;
//--- 配列を開放する
   ::ArrayFree(m_dd_y);
   ::ArrayFree(m_dd_x);
//--- データシリーズのサイズ
   ::ArrayResize(m_columns,elements_total);
   for(int i=0; i<elements_total; i++)
      ::ArrayResize(m_columns[i].m_rows,strings_total-1);
//--- ファイルからのデータを配列に書き入れる
   for(int r=0; r<strings_total-1; r++)
     {
      ::StringSplit(m_source_data[r+1],u_sep,str_elements);
      for(int c=0; c<elements_total; c++)
         m_columns[c].m_rows[r]=str_elements[c];
     }
...

データソートの準備がすべて整いました。ここでは、銘柄バランス配列のサイズを設定する必要があります。

...
//--- シリーズと列の数
   m_rows_total    =strings_total-1;
   m_columns_total =elements_total;
//--- 1番目の列で時刻によって並び替える
   QuickSort(0,m_rows_total-1,0);
//--- シリーズサイズ
   ::ArrayResize(m_symbol_balance,m_symbols_total);
   for(int i=0; i<m_symbols_total; i++)
      ::ArrayResize(m_symbol_balance[i].m_data,m_rows_total);
...

次に、総残高と損失率の配列を記入します。預金の補充に関連するすべての取引は抜かされます

...
//--- 残高と最大損失率
   double balance      =0.0;
   double max_drawdown =0.0;
//--- 合計残高データを取得する
   for(uint i=0; i<m_rows_total; i++)
     {
      //--- 当初入金
      if(i==0)
        {
         balance+=(double)m_columns[elements_total-1].m_rows[i];
         m_symbol_balance[0].m_data[i]=balance;
        }
      else
        {
         //--- 補充を抜かす
         if(m_columns[1].m_rows[i]=="Balance")
            m_symbol_balance[0].m_data[i]=m_symbol_balance[0].m_data[i-1];
         else
           {
            balance+=(double)m_columns[elements_total-1].m_rows[i]+(double)m_columns[elements_total-2].m_rows[i]+(double)m_columns[elements_total-3].m_rows[i];
            m_symbol_balance[0].m_data[i]=balance;
           }
        }
      //--- 損失率を計算する
      if(MaxDrawdownToString(i,balance,max_drawdown)!="")
         AddDrawDown(i,max_drawdown);
     }
...

そして、各銘柄の残高配列を記入します。 

...
//--- 銘柄残高データを取得する
   for(int s=1; s<m_symbols_total; s++)
     {
      //--- 当初入金
      balance=m_symbol_balance[0].m_data[0];
      m_symbol_balance[s].m_data[0]=balance;
      //---
      for(uint r=0; r<m_rows_total; r++)
        {
         //--- 銘柄が一致しない場合は以前の値
         if(m_symbols_name[s]!=m_columns[m_symbol_index].m_rows[r])
           {
            if(r>0)
               m_symbol_balance[s].m_data[r]=m_symbol_balance[s].m_data[r-1];
            //---
            continue;
           }
         //--- 取引結果が0でない場合
         if((double)m_columns[elements_total-1].m_rows[r]!=0)
           {
            balance+=(double)m_columns[elements_total-1].m_rows[r]+(double)m_columns[elements_total-2].m_rows[r]+(double)m_columns[elements_total-3].m_rows[r];
            m_symbol_balance[s].m_data[r]=balance;
           }
         //--- その他の場合は前の値を書く
         else
            m_symbol_balance[s].m_data[r]=m_symbol_balance[s].m_data[r-1];
        }
     }
...

その後、データはグラフィカルインターフェイスでグラフに表示されます。さまざまなシグナルプロバイダーの例を以下に示します。

 図8 結果の表示(例1)

図8 結果の表示(例1)

 図9 結果の表示(例2)

図9 結果の表示(例2)

 図10 結果の表示(例3)

図10 結果の表示(例3)

 図11 結果の表示(例4)

図11 結果の表示(例4)

終わりに

本稿では、複数銘柄残高グラフを表示するためのMQLアプリケーションの最新版を示しています。これまでは、この結果を得るにはサードパーティのプログラムを使用する必要がありましたが、MetaTrader 5を離れることなく、すべてをMQLだけで実装できるようになりました。

以下では、本稿で提供されているテストとコードの詳細な調査のためのファイルをダウンロードできます。各プログラムバージョンには、次のファイル構造があります。 

ファイル名 コメント
MacdSampleMultiSymbols.mq5 標準パッケージのEA(MACD Sample)を修正したもの
Program.mqh プログラムクラスを持つファイル
CreateGUI.mqh Program.mqhファイル内のプログラムクラスからメソッドを実装するファイル
Strategy.mqh 変更されたMACDサンプル戦略クラス(複数銘柄バージョン)のファイル
FormatString.mqh 文字列の書式設定の補助機能を持つファイル

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

添付されたファイル |
MQL5.zip (39.77 KB)
1つのツールで複数のチャートを異なる時間枠で同期させる 1つのツールで複数のチャートを異なる時間枠で同期させる
取引の決定を行う際に、取引の過程で複数の時間枠でチャートを同時に分析する必要があることが多々あります。また、チャート上にはグラフィック分析のオブジェクトがあるため、すべてのチャートに同じオブジェクトを適用するのは不便です。この記事では、チャート上のオブジェクトの複製の自動化をご紹介したいと思います。
任意の複雑さのレベルのグラフィカルなパネルを作成する方法 任意の複雑さのレベルのグラフィカルなパネルを作成する方法
この記事では、CAppDialog クラスに基づいてパネルを作成する方法と、パネルにコントロールを追加する方法について詳しく説明します。 パネルの構造とオブジェクトの継承を示すスキームを提供します。 この記事では、イベントの処理方法、および依存コントロールへの配信方法についても説明します。 その他の例では、サイズや背景色などのパネルパラメータを編集する方法を示します。
ZUP-Pesavento パターンと普遍的なジグザグ。 パターンの検索 ZUP-Pesavento パターンと普遍的なジグザグ。 パターンの検索
ZUP インジケータープラットフォームでは、既に設定されている複数の既知のパターンを検索できます。 これらのパラメータは、要件に合わせて編集できます。 また、ZUP グラフィカルインターフェイスを使用して新しいパターンを作成し、そのパラメータをファイルに保存することもできます。 その後、 新しいパターンがチャート上で見つけることができるかどうか、すぐにチェックすることができます。
グラフィカルインタフェースを通して最適化の結果を処理する グラフィカルインタフェースを通して最適化の結果を処理する
最適化結果の分析と処理についての話を展開していきます。今回の課題は、100の最良の最適化結果を選択し、それらをグラフィカルインタフェースの表に表示することです。ユーザーが最適化結果の表で列を選択しつつ、残高とドローダウンのマルチシンボルのグラフを別々に入手できるようにします。