English Русский 中文 Español Deutsch Português
チャート上で選択したシグナルの取引を分析する方法

チャート上で選択したシグナルの取引を分析する方法

MetaTrader 5 | 25 6月 2018, 09:42
2 735 0
Dmitriy Gizlyk
Dmitriy Gizlyk

目次

概論

シグナルサービスでは、有料、無料を問わず新しいシグナルが登場しています。MetaTraderチームは、ターミナルから離れることなく、サービスを利用できるようにしました。残すは、最小のリスクで最大の利益を得られるシグナルを選ぶのみです。この問題は長い間議論されてきました。特定の基準によって自動的にシグナルを選択する方法は既に提案されています[1]。しかし、民衆の知恵に言われているように、百聞は一見に如かずです。この記事では、選んだシグナルのチャート上での取引履歴を研究して分析する方法をご紹介します。このアプローチにより、より良く取引戦略を理解しリスクを評価することができるようになるかもしれません。 

1. 目標の定義

ターミナルにすでにチャートの取引履歴を表示する機能がある場合、何のために新たに開発を行う必要があるのかという疑問が聞こえてきそうです。何しろ関心があるシグナルを選択し、ターミナル内のボタンをクリックするだけですむわけですから。

『トレードをチャートに表示する』コマンド

その後、使用されるシグナルの数に応じて、完了した取引のマークのついた新しいウィンドウが端末で開かれます。もちろん、チャートをスクロールして取引を探すため、この作業はかなり面倒です。さらに、異なるチャート上での取引は時間が一致する可能性があり、個々のチャートの分析ではこれは分かりません。この段階で、作業の一部を自動化しようとします。

取得したチャートを分析する為に必要なツールを決めるには、私達にはどんなデータが必要なのかを理解する必要があります。私が求めるものの要点は次の通りです。

  • 異なるツール上でシグナルがどれくらいスムーズに動作しているかが分かること。
  • デポジットへの分配と同様に、いくつのポジションを同時に開くことができるか。
  • シグナルが複数のポジションを同時に開く場合、デポジットへの負荷をヘッジまたは増強するのか。
  • どんな時にどのツールで最大のドローダウンが起こるか。
  • どんな時に最大の利益がでるのか。

2. 取引統計の収集

2.1. オーダー情報を保存するクラス

関心のあるシグナルを選択し、チャートに取引履歴を表示します。私たちは初期データを収集し、後で分析を行います。特定のオーダーごとにデータを記録するために、CObjectクラスに基づいてCOrderクラスを作成します。このクラスの変数では、取引チケット、取引の量と種類、取引価格、取引タイプ(入/出)、取引の開始時刻、そして当然ながらツールを保存します。

class COrder : public CObject
  {
private:
   long                 l_Ticket;
   double               d_Lot;
   double               d_Price;
   ENUM_POSITION_TYPE   e_Type;
   ENUM_DEAL_ENTRY      e_Entry;
   datetime             dt_OrderTime;
   string               s_Symbol;
   
public:
                        COrder();
                       ~COrder();
   bool                 Create(string symbol, long ticket, double volume, double price, datetime time, ENUM_POSITION_TYPE type);
//---
   string               Symbol(void)   const {  return s_Symbol;     }
   long                 Ticket(void)   const {  return l_Ticket;     }
   double               Volume(void)   const {  return d_Lot;        }
   double               Price(void)    const {  return d_Price;      }
   datetime             Time(void)     const {  return dt_OrderTime; } 
   ENUM_POSITION_TYPE   Type(void)           {  return e_Type;       }
   ENUM_DEAL_ENTRY      DealEntry(void)const {  return e_Entry;      }
   void                 DealEntry(ENUM_DEAL_ENTRY value) {  e_Entry=value; }
//--- methods for working with files
   virtual bool         Save(const int file_handle);
   virtual bool         Load(const int file_handle);
//---
   //--- method of comparing the objects
   virtual int          Compare(const CObject *node,const int mode=0) const;
  };

データへのアクセス機能に加えて、オーダーをソートするのに必要な類似のオブジェクトとの比較機能だけでなく、データの保存および次の読み取りのためにファイルを操作するオーダークラスの機能を追加します。

2つのオーダーを比較するには、仮想比較機能を書きかえる必要があります。これは、2つのCObjectオブジェクトを比較するように設計された基本クラスの関数です。したがって、そのパラメータでは、CObjectオブジェクトへの参照とソート方法が渡されます。(実行日を増やすことによって)一方向のみでオーダーをソートするので、関数のコードではmodeパラメータを使用しません。しかし、参照によって取得されたCOrderオブジェクトを操作するには、最初に適切な型に移す必要があります。その後、取得したオーダーと現在のオーダーの日付を比較します。取得したオーダーの方がが古い場合は、 "-1"を返し、新しければ "1"を返します。オーダー実行日が等しい場合、関数は "0"を返します。

int COrder::Compare(const CObject *node,const int mode=0) const
  {
   const COrder *temp=GetPointer(node);
   if(temp.Time()>dt_OrderTime)
      return -1;
//---
   if(temp.Time()<dt_OrderTime)
      return 1;
//---
   return 0;
  }

2.2. チャートからの情報の収集

オーダーを処理するには、CArrayObjクラスに基づいてCOrdersCollectionというクラスを作成します。ここに情報を収集し処理します。データを格納するために、特定の順序で直接作業するためのオブジェクトのインスタンスと、使用されたツールのリストを格納する配列を宣言します。基本クラスの関数を使用しオーダーの配列を格納します。

class COrdersCollection : public CArrayObj
  {
private:
   COrder            *Temp;
   string            ar_Symbols[];
   
public:

                     COrdersCollection();
                    ~COrdersCollection();
// --- 初期化
   bool              Create(void);
// --- オーダーの追加
   bool              Add(COrder *element);
// --- データへのアクセス
   int               Symbols(string &array[]);
   bool              GetPosition(const string symbol, const datetime time, double &volume, double &price, ENUM_POSITION_TYPE &type);
   datetime          FirstOrder(const string symbol=NULL);
   datetime          LastOrder(const string symbol=NULL);
// --- タイムシリーズを取得する
   bool              GetTimeSeries(const string symbol, const datetime start_time, const datetime end_time, const int direct,
                                   double &balance[], double &equity[], double &time[], double &profit, double &loss,int &long_trades, int &short_trades);
//---
   void              SetDealsEntry(void);
  };

Create関数が、データ収集を担当します。このメソッドの本体に、端末で開いているすべてのチャートを検索するサイクルを構成します。各チャートで、OBJ_ARROW_BUYおよびOBJ_ARROW_SELLタイプのグラフィックオブジェクトを検索します。

bool COrdersCollection::Create(void)
  {
   long chart=ChartFirst();
   while(chart>0)
     {
      int total_buy=ObjectsTotal(chart,0,OBJ_ARROW_BUY);
      int total_sell=ObjectsTotal(chart,0,OBJ_ARROW_SELL);
      if((total_buy+total_sell)<=0)
        {
         chart=ChartNext(chart);
         continue;
        }

チャート上にオブジェクトが見つかった場合は、チャートのシンボルをツールの配列に追加します(ただし、既に保存されているものの中にこのようなツールがあるかどうかを事前に確認してください)。

      int symb=ArraySize(ar_Symbols);
      string symbol=ChartSymbol(chart);
      bool found=false;
      for(int i=0;(i<symb && !found);i++)
         if(ar_Symbols[i]==symbol)
           {
            found=true;
            symb=i;
            break;
           }
      if(!found)
        {
         if(ArrayResize(ar_Symbols,symb+1,10)<=0)
            return false;
         ar_Symbols[symb]=symbol;
        }

次に、チャートから取引に関する情報の収集をデータ配列に構成します。私たちが持っている取引に関する唯一の情報源は、グラフィックオブジェクトであることに注意をしてください。オブジェクトのパラメータからは、取引の時間と価格のみを得ることができます。オブジェクトの名前から抽出する必要がある残りの情報は、すべてテキスト文字列です。

グラフィックオブジェクトの名前

この図は、オブジェクトの名前が取引に関するすべての情報をスペースで区切って収集されていることを示しています。この観測を使って、文字列要素の配列で文字列をスペースで分割してみましょう。次に、対応する要素からの情報を、必要なデータタイプへ引き渡し保存します。情報を収集した後、次のチャートへ進みます。

      int total=fmax(total_buy,total_sell);
      for(int i=0;i<total;i++)
        {
         if(i<total_buy)
           {
            string name=ObjectName(chart,i,0,OBJ_ARROW_BUY);
            datetime time=(datetime)ObjectGetInteger(chart,name,OBJPROP_TIME);
            StringTrimLeft(name);
            StringTrimRight(name);
            StringReplace(name,"#","");
            string split[];
            StringSplit(name,' ',split);
            Temp=new COrder;
            if(CheckPointer(Temp)!=POINTER_INVALID)
              {
               if(Temp.Create(ar_Symbols[symb],StringToInteger(split[1]),StringToDouble(split[3]),StringToDouble(split[6]),time,POSITION_TYPE_BUY))
                  Add(Temp);
              }
           }
//---
         if(i<total_sell)
           {
            string name=ObjectName(chart,i,0,OBJ_ARROW_SELL);
            datetime time=(datetime)ObjectGetInteger(chart,name,OBJPROP_TIME);
            StringTrimLeft(name);
            StringTrimRight(name);
            StringReplace(name,"#","");
            string split[];
            StringSplit(name,' ',split);
            Temp=new COrder;
            if(CheckPointer(Temp)!=POINTER_INVALID)
              {
               if(Temp.Create(ar_Symbols[symb],StringToInteger(split[1]),StringToDouble(split[3]),StringToDouble(split[6]),time,POSITION_TYPE_SELL))
                  Add(Temp);
              }
           }
        }
      chart=ChartNext(chart);
     }

グラフィックラベルには、取引ごとにポジションへのエントリまたはポジションからの撤退があるのかについての情報はありません。したがって、今までは取引に関する情報の保存時、このフィールドは空白のままでした。ここでチャートからすべてのラベルを収集したら、SetDealsEntry関数を呼び出して欠落した情報を入力します。

   SetDealsEntry();
//---
   return true;
  }

データベース内での取引の重複を避けるため、Add関数を書き直し、チケットオーダーの有無をチェックします。

bool COrdersCollection::Add(COrder *element)
  {
   for(int i=0;i<m_data_total;i++)
     {
      Temp=m_data[i];
      if(Temp.Ticket()==element.Ticket())
         return true;
     }
//---
   return CArrayObj::Add(element);
  }

操作タイプを取引内に配置するために、SetDealsEntry関数を作成します。最初は、基底クラスのソート関数を呼び出します。次に、それぞれのツールと取引を検索するサイクルを構成します。操作の種類を決定するアルゴリズムは簡単です。操作時に開かれたポジションがない、または取引と同じ方向にある場合は、そのポジションのエントリとして取引を定義します。操作が既存のポジションとは反対である場合、開いているポジションを終了するためにそのボリュームが使用され、残りは新しいポジションを開きます(MettTrader5のネッティングシステムと同様)。

COrdersCollection::SetDealsEntry(void)
  {
   Sort(0);
//---
   int symbols=ArraySize(ar_Symbols);
   for(int symb=0;symb<symbols;symb++)
     {
      double volume=0;
      ENUM_POSITION_TYPE type=-1;
      for(int ord=0;ord<m_data_total;ord++)
        {
         Temp=m_data[ord];
         if(Temp.Symbol()!=ar_Symbols[symb])
            continue;
//---
         if(volume==0 || type==Temp.Type())
           {
            Temp.DealEntry(DEAL_ENTRY_IN);
            volume=NormalizeDouble(volume+Temp.Volume(),2);
            type=Temp.Type();
           }
         else
           {
            if(volume>=Temp.Volume())
              {
               Temp.DealEntry(DEAL_ENTRY_OUT);
               volume=NormalizeDouble(volume-Temp.Volume(),2);
              }
            else
              {
               Temp.DealEntry(DEAL_ENTRY_INOUT);
               volume=NormalizeDouble(volume-Temp.Volume(),2);
               type=Temp.Type();
              }
           }
        }
     }
  }

2.3. 各ツールごとにバランスと資金のタイムシリーズを作成する

その後、各ツールのバランスシートを構築するためには、分析期間を通じてこれらのパラメータの計算を含むタイムシリーズを作成する必要があります。分析時に分析する期間を変更することが可能であることが望ましいと思います。これにより、限られた時間間隔でシグナルの動作を調べることができます。

GetTimeSeries関数でタイムシリーズを数えます。そのパラメータでは、ポジションの長短を観測するために、商品や、分析期間の開始と終了の時間、および取引の方向を明示します。関数は、バランス、資金とタイムスタンプの3つのタイムシリーズを返します。また、分析した期間の収益、損失、取引の長短の数をツールの統計情報に返します。

先に急ぎ、タイムスタンプのタイムシリーズの配列がdoubleと定義されているという事に注目します。この小さなトリックは強制的な措置です。次に、バランスチャートは、double型の配列のみを受け付ける標準のCGraphicクラスを使用して作成されます。

関数の始めに、変数をリセットして統計を収集し、指定したシンボルの正しさをチェックし、価格変化の1ポイントの価格を取得します。

bool COrdersCollection::GetTimeSeries(const string symbol,const datetime start_time,const datetime end_time,const int direct,double &balance[],double &equity[], double &time[], double &profit, double &loss,int &long_trades, int &short_trades)
  {
   profit=loss=0;
   long_trades=short_trades=0;
//---
   if(symbol==NULL)
      return false;
//---
   double tick_value=SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE)/SymbolInfoDouble(symbol,SYMBOL_POINT);
   if(tick_value==0)
      return false;

タイムシリーズの構築のために、私たちはタイムフレームM5からのツールの価格を使用します。つまり、それらをダウンロードする必要があります。しかし、要求した価格はまだ形成されていない可能性があります。もう一つのトリックがあります。取引をループせずにデータのロードが完了するまで待ちます。これはプログラムの実行を完全に停止し、インジケーターで使用するとターミナルを遅くすることがあります。最初の呼び出しが失敗した後、関数を終了しますが、その前にデータを更新する関数を後で呼び出すカスタムイベントを作成します。

   ENUM_TIMEFRAMES timeframe=PERIOD_M5;
//---
   double volume=0;
   double price=0;
   ENUM_POSITION_TYPE type=-1;
   int order=-1;
//---
   MqlRates rates[];
   int count=0;
   count=CopyRates(symbol,timeframe,start_time,end_time,rates);
   if(count<=0 && !ReloadHistory)
     {
      //--- send notification
      ReloadHistory=EventChartCustom(CONTROLS_SELF_MESSAGE,1222,0,0.0,symbol);
      return false;
     }

価格をダウンロードした後、ダウンロードした価格の​​サイズに従って、タイムシリーズの配列のサイズを指定します。

   if(ArrayResize(balance,count)<count || ArrayResize(equity,count)<count || ArrayResize(time,count)<count)
      return false;
   ArrayInitialize(balance,0);

次に、タイムシリーズの情報を収集するサイクルを編成します。各バーで、完了した操作を定義します。もしこれがポジションを開く操作であれば、現在のポジションのボリュームを増やして平均開始値を再計算します。これがポジションを終了する操作であれば、操作から得た利益/損失を計算し、結果の値が現在のバーの残高変更に加算され、現在のポジションのボリュームを減らします。その後、バーの閉鎖時間に終了していないポジションのボリュームについて、利益/損失を計算し、得られた値を分析するバーの資金の変更に保存します。履歴全体を検索した後、関数を終了します。

   do
     {
      order++;
      if(order<m_data_total)
         Temp=m_data[order];
      else
         Temp=NULL;
     }
   while(CheckPointer(Temp)==POINTER_INVALID && order<m_data_total);
//---
   for(int i=0;i<count;i++)
     {
      while(order<m_data_total && Temp.Time()<(rates[i].time+PeriodSeconds(timeframe)))
        {
         if(Temp.Symbol()!=symbol)
           {
            do
              {
               order++;
               if(order<m_data_total)
                  Temp=m_data[order];
               else
                  Temp=NULL;
              }
            while(CheckPointer(Temp)==POINTER_INVALID && order<m_data_total);
            continue;
           }
//---
         if(Temp!=NULL)
           {
            if(type==Temp.Type())
              {
               price=volume*price+Temp.Volume()*Temp.Price();
               volume+=Temp.Volume();
               price=price/volume;
               switch(type)
                 {
                  case POSITION_TYPE_BUY:
                    long_trades++;
                    break;
                  case POSITION_TYPE_SELL:
                    short_trades++;
                    break;
                 }
              } 
            else
              {
               if(i>0 && (direct<0 || direct==type))
                 {
                  double temp=(Temp.Price()-price)*tick_value*(type==POSITION_TYPE_BUY ? 1 : -1)*MathMin(volume,Temp.Volume());
                  balance[i]+=temp;
                  if(temp>=0)
                     profit+=temp;
                  else
                     loss+=temp;
                 }
               volume-=Temp.Volume();
               if(volume<0)
                 {
                  volume=MathAbs(volume);
                  price=Temp.Price();
                  type=Temp.Type();
                  switch(type)
                    {
                     case POSITION_TYPE_BUY:
                       long_trades++;
                       break;
                     case POSITION_TYPE_SELL:
                       short_trades++;
                       break;
                    }
                 }
              }
           }
         do
           {
            order++;
            if(order<m_data_total)
               Temp=m_data[order];
            else
               Temp=NULL;
           }
         while(CheckPointer(Temp)==POINTER_INVALID && order<m_data_total);
        }
      if(i>0)
        {
         balance[i]+=balance[i-1];
        }
      if(volume>0 && (direct<0 || direct==type))
         equity[i]=(rates[i].close-price)*tick_value*(type==POSITION_TYPE_BUY ? 1 : -1)*MathMin(volume,(Temp!=NULL ? Temp.Volume(): DBL_MAX));
      else
         equity[i]=0;
      equity[i]+=balance[i];
      time[i]=(double)rates[i].time;
     }
//---
   return true;
  }

クラスとメソッドの完全なコードは添付ファイルから利用できます。
 

3.外観の追加

プログラムのグラフィカルインターフェイスには、分析の開始日と終了日、チャートに表示される情報を選択するためのチェックボックス、統計ブロック、およびチャート本体が含まれます。

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

グラフィカルインターフェイスはCStatisticsPanelクラス(これはCAppDialogクラスの後継です)に組み込まれています。分析の開始日と終了日を選択するには、CDatePickerクラスのインスタンスを使用します。表示された情報を選択するためのチェックボックスは3つのグループに分類されます。

  • バランスと資金。
  • ロングポジションとショートポジション。
  • 分析される商品のリスト。

3.1. グラフィカルパネルの作成

チェックボックスのブロックを作成するために、CCheckGroupクラスのインスタンスを使用します。テキスト統計は、CLabelクラスのインスタンスを使用して表示します。CGraphicクラスのインスタンスを使用してチャートを作成します。もちろん、オーダーの統計にアクセスする為に、COrdersCollectionクラスのインスタンスを宣言します。

class CStatisticsPanel : public CAppDialog
  {
private:
   CDatePicker       StartDate;
   CDatePicker       EndDate;
   CLabel            Date;
   CGraphic          Graphic;
   CLabel            ShowLabel;
   CCheckGroup       Symbols;
   CCheckGroup       BalEquit;
   CCheckGroup       Deals;
   string            ar_Symbols[];
   CLabel            TotalProfit;
   CLabel            TotalProfitVal;
   CLabel            GrossProfit;
   CLabel            GrossProfitVal;
   CLabel            GrossLoss;
   CLabel            GrossLossVal;
   CLabel            TotalTrades;
   CLabel            TotalTradesVal;
   CLabel            LongTrades;
   CLabel            LongTradesVal;
   CLabel            ShortTrades;
   CLabel            ShortTradesVal;
   //---
   COrdersCollection Orders;

public:
                     CStatisticsPanel();
                    ~CStatisticsPanel();
   //--- main application dialog creation and destroy
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   virtual void      Destroy(const int reason=REASON_PROGRAM);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   virtual bool      CreateLineSelector(const string name,const int x1,const int y1,const int x2,const int y2);
   virtual bool      CreateDealsSelector(const string name,const int x1,const int y1,const int x2,const int y2);
   virtual bool      CreateCheckGroup(const string name,const int x1,const int y1,const int x2,const int y2);
   virtual bool      CreateGraphic(const string name,const int x1,const int y1,const int x2,const int y2);
   //---
   virtual void      Maximize(void);
   virtual void      Minimize(void);
   //---
   virtual bool      UpdateChart(void);

  };

Createメソッドでは、最初に親クラスの対応するメソッドを呼び出し、その場所にすべてのオブジェクトを配置し、オーダーを集めるクラスのインスタンスを初期化します。各要素を初期化した後、初期値を割り当ててオブジェクトをコントロール要素の集積に追加することを忘れないでください。基本クラスの操作の詳細については、記事[2]および[3]の中でお話しているので、ここでは詳細は書かず、そのコードのみ記載します。

bool CStatisticsPanel::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return false;
//---
   if(!TotalProfit.Create(m_chart_id,m_name+"Total Profit",m_subwin,5,80,115,95))
      return false;
   if(!TotalProfit.Text("Total Profit"))
      return false;
   if(!Add(TotalProfit))
      return false;
//---
   if(!TotalProfitVal.Create(m_chart_id,m_name+"Total Profit Value",m_subwin,135,80,250,95))
      return false;
   if(!TotalProfitVal.Text("0"))
      return false;
   if(!Add(TotalProfitVal))
      return false;
//---
   if(!GrossProfit.Create(m_chart_id,m_name+"Gross Profit",m_subwin,5,100,115,115))
      return false;
   if(!GrossProfit.Text("Gross Profit"))
      return false;
   if(!Add(GrossProfit))
      return false;
//---
   if(!GrossProfitVal.Create(m_chart_id,m_name+"Gross Profit Value",m_subwin,135,100,250,115))
      return false;
   if(!GrossProfitVal.Text("0"))
      return false;
   if(!Add(GrossProfitVal))
      return false;
//---
   if(!GrossLoss.Create(m_chart_id,m_name+"Gross Loss",m_subwin,5,120,115,135))
      return false;
   if(!GrossLoss.Text("Gross Loss"))
      return false;
   if(!Add(GrossLoss))
      return false;
//---
   if(!GrossLossVal.Create(m_chart_id,m_name+"Gross Loss Value",m_subwin,135,120,250,135))
      return false;
   if(!GrossLossVal.Text("0"))
      return false;
   if(!Add(GrossLossVal))
      return false;
//---
   if(!TotalTrades.Create(m_chart_id,m_name+"Total Trades",m_subwin,5,150,115,165))
      return false;
   if(!TotalTrades.Text("Total Trades"))
      return false;
   if(!Add(TotalTrades))
      return false;
//---
   if(!TotalTradesVal.Create(m_chart_id,m_name+"Total Trades Value",m_subwin,135,150,250,165))
      return false;
   if(!TotalTradesVal.Text("0"))
      return false;
   if(!Add(TotalTradesVal))
      return false;
//---
   if(!LongTrades.Create(m_chart_id,m_name+"Long Trades",m_subwin,5,170,115,185))
      return false;
   if(!LongTrades.Text("Long Trades"))
      return false;
   if(!Add(LongTrades))
      return false;
//---
   if(!LongTradesVal.Create(m_chart_id,m_name+"Long Trades Value",m_subwin,135,170,250,185))
      return false;
   if(!LongTradesVal.Text("0"))
      return false;
   if(!Add(LongTradesVal))
      return false;
//---
   if(!ShortTrades.Create(m_chart_id,m_name+"Short Trades",m_subwin,5,190,115,215))
      return false;
   if(!ShortTrades.Text("Short Trades"))
      return false;
   if(!Add(ShortTrades))
      return false;
//---
   if(!ShortTradesVal.Create(m_chart_id,m_name+"Short Trades Value",m_subwin,135,190,250,215))
      return false;
   if(!ShortTradesVal.Text("0"))
      return false;
   if(!Add(ShortTradesVal))
      return false;
//---
   if(!Orders.Create())
      return false;
//---
   if(!ShowLabel.Create(m_chart_id,m_name+"Show Selector",m_subwin,285,8,360,28))
      return false;
   if(!ShowLabel.Text("Symbols"))
      return false;
   if(!Add(ShowLabel))
      return false;
   if(!CreateLineSelector("LineSelector",2,30,115,70))
      return false;
   if(!CreateDealsSelector("DealsSelector",135,30,250,70))
      return false;
   if(!CreateCheckGroup("CheckGroup",260,30,360,ClientAreaHeight()-5))
      return false;
//---
   if(!Date.Create(m_chart_id,m_name+"->",m_subwin,118,8,133,28))
      return false;
   if(!Date.Text("->"))
      return false;
   if(!Add(Date))
      return false;
//---
   if(!StartDate.Create(m_chart_id,m_name+"StartDate",m_subwin,5,5,115,28))
      return false;
   if(!Add(StartDate))
      return false;
//---
   if(!EndDate.Create(m_chart_id,m_name+"EndDate",m_subwin,135,5,250,28))
      return false;
   if(!Add(EndDate))
      return false;
//---
   StartDate.Value(Orders.FirstOrder());
   EndDate.Value(Orders.LastOrder());
//---
   if(!CreateGraphic("Chraphic",370,5,ClientAreaWidth()-5,ClientAreaHeight()-5))
      return false;
//---
   UpdateChart();
//---
   return true;
  }

注意深い読者は、作成されるチャートがコントロール要素のコレクションに追加されていないことに気付くかもしれません。これは、CGraphicオブジェクトがCWndクラスから継承されず、CWnd継承オブジェクトのみがコレクションに追加されるためです。したがって、パネルを折りたたんだり展開したりする機能を書き直さなければなりません。
すべてのオブジェクトを初期化した後、関数を呼び出してチャートを更新します。

3.2. グラフィックス作成関数

CreateGraphicチャート作成関数について簡単に説明しましょう。パラメータでは、作成するオブジェクトの名前とチャート位置の座標を取得します。関数の初めにチャートが直接作成されます(CGraphicクラスのCreate関数を呼び出します)。CGraphicクラスはCWndクラスから継承されていないため、パネルのコントロール要素のコレクションに追加することはできません。チャートの座標は、クライアント領域の位置に従って直ちに置き換えられます。

bool CStatisticsPanel::CreateGraphic(const string name,const int x1,const int y1,const int x2,const int y2)
  {
   if(!Graphic.Create(m_chart_id,m_name+name,m_subwin,ClientAreaLeft()+x1,ClientAreaTop()+y1,ClientAreaLeft()+x2,ClientAreaTop()+y2))
      return false;

次に、チャートに表示される各曲線のCCurveクラスのインスタンスを作成する必要があります。これを行うために、まずCOrdersCollectionクラスのインスタンスから使用されているツールのリストを取得します。次に、ループ内で、各ツールの曲線と平均を作成し、空の配列で初期化します。作成後、データが受信されるまでチャートから線を隠します。

   int total=Orders.Symbols(ar_Symbols);
   CColorGenerator ColorGenerator;
   double array[];
   ArrayFree(array);
   for(int i=0;i<total;i++)
     {
      //---
      CCurve *curve=Graphic.CurveAdd(array,array,ColorGenerator.Next(),CURVE_LINES,ar_Symbols[i]+" Balance");
      curve.Visible(false);
      curve=Graphic.CurveAdd(array,array,ColorGenerator.Next(),CURVE_LINES,ar_Symbols[i]+" Equity");
      curve.Visible(false);
     }

曲線を作成した後、横軸スケールのオートスケーリングを無効にして、表示プロパティを日付の形式で表示します。また、曲線のシグネチャのサイズを表示し、チャートを画面に表示します。

   CAxis *axis=Graphic.XAxis();
   axis.AutoScale(false);
   axis.Type(AXIS_TYPE_DATETIME);
   axis.ValuesDateTimeMode(TIME_DATE);
   Graphic.HistorySymbolSize(20);
   Graphic.HistoryNameSize(10);
   Graphic.HistoryNameWidth(60);
   Graphic.CurvePlotAll();
   Graphic.Update();
//---
   return true;
  }

3.3. グラフと統計データを更新する方法

UpdateChartメソッドによってシグナルに関するデータを更新します。関数の始めに、データ収集のための変数と配列を準備します。

bool CStatisticsPanel::UpdateChart(void)
  {
   double balance[];
   double equity[];
   double time[];
   double total_profit=0, total_loss=0;
   int total_long=0, total_short=0;
   CCurve *Balance, *Equity;

それから分析した期間の開始日と終了日を取得します。

   datetime start=StartDate.Value();
   datetime end=EndDate.Value();

ロングポジションとショートポジションの統計を表示するためのマークを確認しましょう。

   int deals=-2;
   if(Deals.Check(0))
      deals=(Deals.Check(1) ? -1 : POSITION_TYPE_BUY);
   else
      deals=(Deals.Check(1) ? POSITION_TYPE_SELL : -2);

各ツールのループで初期データを準備したら、既に知っているGetTimeSeries関数を呼び出してタイムシリーズを更新します。メソッドを呼び出す前に、対応するシンボルのチェックボックスにチェックマークを付けてください。それがない場合、メソッドは呼び出されず、曲線は非表示になります。タイムシリーズのデータの受信が成功した後、バランスと資金の曲線データを更新し、事前にチェックボックスに対応したマークをチェックします。マークがない場合、曲線はチャートから非表示になります。

   int total=ArraySize(ar_Symbols);
   for(int i=0;i<total;i++)
     {
      Balance  =  Graphic.CurveGetByIndex(i*2);
      Equity   =  Graphic.CurveGetByIndex(i*2+1);
      double profit,loss;
      int long_trades, short_trades;
      if(deals>-2 && Symbols.Check(i) && Orders.GetTimeSeries(ar_Symbols[i],start,end,deals,balance,equity,time,profit,loss,long_trades,short_trades))
        {
         if(BalEquit.Check(0))
           {
            Balance.Update(time,balance);
            Balance.Visible(true);
           }
         else
            Balance.Visible(false);
         if(BalEquit.Check(1))
           {
            Equity.Update(time,equity);
            Equity.Visible(true);
           }
         else
            Equity.Visible(false);
         total_profit+=profit;
         total_loss+=loss;
         total_long+=long_trades;
         total_short+=short_trades;
        }
      else
        {
         Balance.Visible(false);
         Equity.Visible(false);
        }
     }

次のステップは、チャートの分析期間の開始日と終了日、およびグリッド間隔を指定することです。Обновим график. 

   CAxis *axis=Graphic.XAxis();
   axis.Min((double)start);
   axis.Max((double)end);
   axis.DefaultStep((end-start)/5);
   if(!Graphic.Redraw(true))
      return false;
   Graphic.Update();

メソッドの終わりに、テキストラベル内の情報を更新して、シグナルの統計情報を表示します。

   if(!TotalProfitVal.Text(DoubleToString(total_profit+total_loss,2)))
      return false;
   if(!GrossProfitVal.Text(DoubleToString(total_profit,2)))
      return false;
   if(!GrossLossVal.Text(DoubleToString(total_loss,2)))
      return false;
   if(!TotalTradesVal.Text(IntegerToString(total_long+total_short)))
      return false;
   if(!LongTradesVal.Text(IntegerToString(total_long)))
      return false;
   if(!ShortTradesVal.Text(IntegerToString(total_short)))
      return false;
//---
   return true;
  }

3.4. 「活性化」パネル

パネルを "活性化"させるには、オブジェクトを持つアクションのイベントハンドラを構築する必要があります。プログラムが処理する可能性のあるイベントは何でしょうか?

まず第一に、これは分析期間の開始日または終了日の変更であり、統計の収集およびバランスと資金の曲線の出力を制御するチェックボックスの状態の変更です。私たちのトリックについて忘れないでください。分析したツールのいずれかの引用の履歴を読み込むことが不可能な場合に作成されるカスタムイベントを処理する必要があります。これらのイベントのうちのどれかが発生したら、UpdateChartデータ更新メソッドを呼び出すだけで十分です。最終的に、イベント処理メソッドは次のようになります。

EVENT_MAP_BEGIN(CStatisticsPanel)
   ON_EVENT(ON_CHANGE,Symbols,UpdateChart)
   ON_EVENT(ON_CHANGE,BalEquit,UpdateChart)
   ON_EVENT(ON_CHANGE,Deals,UpdateChart)
   ON_EVENT(ON_CHANGE,StartDate,UpdateChart)
   ON_EVENT(ON_CHANGE,EndDate,UpdateChart)
   ON_NO_ID_EVENT(1222,UpdateChart)
EVENT_MAP_END(CAppDialog)

これらの方法に加えて、パネルの折りたたみ/展開の方法を変更し、チャートの表示/非表示の機能が追加されました。クラスとメソッドの完全なコードは添付ファイルにあります。

4.シグナル分析のためのインジケータの作成

上記のすべてをインジケータの形に集積することを提案したいと思います。これにより、チャート自体に影響を与えることなく、サブウィンドウにグラフィックパネルが作成することができます。

私たちのプログラムのすべての機能はCStatisticsPanelクラスに隠されています。つまり、インジケータを作成するには、このクラスのインスタンスをプログラムで作成すれば十分です。OnInit関数でクラスを初期化します。

int OnInit()
  {
//---
   long chart=ChartID();
   int subwin=ChartWindowFind();
   IndicatorSetString(INDICATOR_SHORTNAME,"Signal Statistics");
   ReloadHistory=false;
//---
   Dialog=new CStatisticsPanel;
   if(CheckPointer(Dialog)==POINTER_INVALID)
     {
      ChartIndicatorDelete(chart,subwin,"Signal Statistics");
      return INIT_FAILED;
     }
   if(!Dialog.Create(chart,"Signal Statistics",subwin,0,0,0,250))
     {
      ChartIndicatorDelete(chart,subwin,"Signal Statistics");
      return INIT_FAILED;
     }
   if(!Dialog.Run())
     {
      ChartIndicatorDelete(chart,subwin,"Signal Statistics");
      return INIT_FAILED;
     }
//---
   return(INIT_SUCCEEDED);
  }

 OnCalculate関数は、プログラムが次のティックの始まりに反応しないため、空のままです。あとはOnDeinitとOnChartEvent関数の対応するメソッドへの呼び出しを追加するだけです。

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   Dialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Dialog.Destroy(reason);
   delete Dialog;
  }

インジケータをコンパイルしたら、選択したシグナルの統計を端末のチャートにロードし、インジケータをチャートの1つに設置すれば十分です。これで、トランザクションの調査と分析が可能になりました。1つ注意すべき点があり、私たちのプログラムでは、分析のためにチャートをフィルタリングしなかった為、インジケータは、端末で開いているすべてのチャートから統計を収集します。インジケータの取引とターミナル内の他の取引との混同を避けるため、シグナルの取引履歴をロードする前に、すべてのチャートを閉じることをお勧めします。

インジケータの動作例

添付ファイルには、プログラムの完全なコードが記載されています。

まとめ

チャート上のラベルで取引を分析するインジケータを作成しました。こういった技術は、シグナルの選択や独自の戦略の最適化など、さまざまな目的に役立ちます。たとえば、これにより、私達の戦略が機能しないツールや、今後これらのシンボルに使用しないツールを特定することができます。

リンク

  1. 有望なシグナルの自動選択
  2. あらゆる複雑なグラフィカルパネルの作成方法とその動作
  3. パネルを改善しましょう(CAppDialog / CWndClientからの継承、背景の色の変更、透明度の追加)

記事で使用されているプログラム:

#
 名前
タイプ 
説明 
1 Order.mqh  クラスライブラリ  取引情報を格納するクラス
2 OrdersCollection.mqh  クラスライブラリ  取引を収集するクラス
3 StatisticsPanel.mqh  クラスライブラリ  GUIクラス
4 SignalStatistics.mq5  インディケータ  取引分析のためのインジケータコード


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

添付されたファイル |
MQL5.zip (470.5 KB)
パネルを改善してみましょう(CAppDialog / CWndClientからの継承、背景の色の変更、透明性の追加) パネルを改善してみましょう(CAppDialog / CWndClientからの継承、背景の色の変更、透明性の追加)
CAppDialogの使用の学習を続けます。ここでは、グラフィックパネルの背景の色、枠線、タイトルを設定する方法を学びます。順を追って、チャート上でアプリケーションウィンドウを移動するときに、アプリケーションウィンドウに透明性を追加する方法を見ていきます。次に、CAppDialogまたはCWndClientから子孫を作成し、コントロールを操作する際の新しい特徴を見ていきます。最後に、新しいプロジェクトを新しい視点から見ていきます。
選択した基準による最適化結果の可視化 選択した基準による最適化結果の可視化
この記事では、前回の記事で始まった最適化結果を扱うMQLアプリケーションの開発を続けます。今回は、グラフィカルインターフェースを介して、別の基準を指定してパラメーターを最適化した後、最良の結果の表を作成する例をご紹介します。
任意のインジケータの計算部分をEAのコードに転送する方法 任意のインジケータの計算部分をEAのコードに転送する方法
インジケータコードをEAに転送する理由は様々です。しかし、このアプローチの長所と短所はどのように評価するべきでしょうか?この記事では、インジケータコードをEAに転送する技術をご紹介します。EAの動作スピードを評価するためにいくつかの実験を行いました。
ZUP-Pesavento パターンと普遍的なジグザグ。 パターンの検索 ZUP-Pesavento パターンと普遍的なジグザグ。 パターンの検索
ZUP インジケータープラットフォームでは、既に設定されている複数の既知のパターンを検索できます。 これらのパラメータは、要件に合わせて編集できます。 また、ZUP グラフィカルインターフェイスを使用して新しいパターンを作成し、そのパラメータをファイルに保存することもできます。 その後、 新しいパターンがチャート上で見つけることができるかどうか、すぐにチェックすることができます。