グラフィカルインターフェイスを備えたエキスパートアドバイザ : 機能の設定(第2部)

Anatoli Kazharski | 27 8月, 2018

目次

概論

前回の記事で、私はエキスパートアドバイザのグラフィックインターフェイスをすばやく作成する方法をご紹介しました。ここでは、この資料を使いながら作業を続け、グラフィカルインターフェイスとエキスパートアドバイザーの機能をリンクします。 

シンボルとインジケータのデータの取得

まず、シンボルとインジケータのデータを受信する必要があります。入力フィールドSymbols filterのフィルタ値を元に、FXシンボル表に集めます。これを担当するのはCProgram::GetSymbols()メソッドです。

メソッドの始めに、シンボルを取得するプロセスが現在進行中であることを進行状況インジケータに示します。始めはシンボルがいくつになるかはわかりません。したがって、インジケータラインは50%に設定します。次に、シンボルの配列を解放します。アプリケーションの操作中に別のシンボルリストを作成する必要がでてくることがある為、CProgram::GetSymbols()メソッドの呼び出し時に毎回これを行う必要があります。

Symbols filter入力フィールドのフィルタは、チェックボックスが有効で、入力フィールドに入力されたテキストシンボルがコンマで入力されている場合にのみ使用されます。これらの条件が満たされた場合、目的のシンボルを検索することができるように、テキスト指定を配列に別々の要素として取得します。念のため、特別なシンボルから各要素の端をクリアします。

次のステップは、FXシンボルの収集サイクルです。これはサーバー上で利用可能なすべてのシンボルの完全なリストが表示されます。各サイクルの開始時に、シンボルの名前を取得して『Market Watch』ウィンドウから削除します。したがって、プログラムのグラフィカルインターフェースとこのウィンドウ内のリストは一致します。次に、受け取ったシンボルがFXシンボルのカテゴリに属しているかどうかを確認しましょう。すべてのシンボルが必要な場合は、この条件をコメントアウトするか削除してください。この記事の中で、私達はFXシンボルのみを取り扱います。

名前フィルタが有効になっている場合、サイクルでは、この繰り返しで取得されたシンボルの名前が、Symbols filter入力フィールドのテキスト指定と一致するかどうかがチェックされます。一致するものがなければ、配列にシンボルを追加します

シンボルが見つからない場合は、メインチャートの現在のシンボルのみが配列に追加されます。その後、配列に追加されたすべてのシンボルが『Market Watch』ウィンドウに表示されます。 

//+------------------------------------------------------------------+
// |アプリケーションを作成するクラス                                     |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   // ---取引のシンボル
   string            m_symbols[];
   //---
private:
   // ---シンボルを取得する
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
// | シンボルを取得する                                                |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(1,2);
   ::Sleep(5);
// ---シンボルの配列を解放する
   ::ArrayFree(m_symbols);
// ---文字列要素の配列
   string elements[];
// ---シンボル名のフィルタ
   if(m_symb_filter.IsPressed())
     {
      string text=m_symb_filter.GetValue();
      if(text!="")
        {
         ushort sep=::StringGetCharacter(",",0);
         ::StringSplit(text,sep,elements);
         //---
         int elements_total=::ArraySize(elements);
         for(int e=0; e<elements_total; e++)
           {
            // ---クリーニング
            ::StringTrimLeft(elements[e]);
            ::StringTrimRight(elements[e]);
           }
        }
     }
// ---FXシンボルの配列を収集する
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      // ---シンボル名を取得する
      string symbol_name=::SymbolName(i,false);
      // ---『Market Watch』ウィンドウで非表示にする
      ::SymbolSelect(symbol_name,false);
      // --- FXシンボルでない場合は、次にすすむ
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      // ---シンボル名のフィルタ
      if(m_symb_filter.IsPressed())
        {
         bool check=false;
         int elements_total=::ArraySize(elements);
         for(int e=0; e<elements_total; e++)
           {
            // --- シンボル名で一致を探す
            if(::StringFind(symbol_name,elements[e])>-1)
              {
               check=true;
               break;
              }
           }
         // ---フィルターを通過しない場合は次へ進む
         if(!check)
            continue;
        }
      // --- シンボルを配列に保存する
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=symbol_name;
     }
// --- 配列が空の場合は、現在のシンボルをデフォルトに設定します
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=::Symbol();
     }
// --- マーケットウォッチウィンドウを表示する
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

ここでCProgram::GetHandles()メソッドを使用して、選択したすべてのシンボルのインジケータハンドルの取得を見ていきましょう。まず、ハンドル配列は、シンボルの配列と同じサイズに設定されます。ハンドルは、タイムフレームコンボボックスに示されているものと同じタイムフレームを取得します。コンボボックスから文字列値を取得できるので、対応する型に変換する必要があります(ENUM_TIMEFRAMES)。サイクルの中で、ハンドルの配列を埋めます。このケースでは、デフォルト値を持つStochasticインジケータです。各反復で、進行状況インジケータを更新します。メソッドの最後に表示されるチャートハンドルの最初のインデックスを保存します。

class CProgram : public CWndEvents
  {
private:
   // ---インジケータハンドル
   int               m_handles[];
   // ---現在のチャートのハンドルのインデックス
   int               m_current_handle_index;
   //---
private:
   // ---ハンドルを取得
   void              GetHandles(void);
  };
//+------------------------------------------------------------------+
// | すべてのシンボルのインジケータハンドルを取得します。                  |
//+------------------------------------------------------------------+
void CProgram::GetHandles(void)
  {
// --- ハンドル配列のサイズを設定する
   int symbols_total=::ArraySize(m_symbols);
   ::ArrayResize(m_handles,symbols_total);
// --- コンボボックスのドロップダウンリストから値を取得する
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
// --- シンボルのリストを通る
   for(int i=0; i<symbols_total; i++)
     {
      // ---インジケータハンドルを取得
      m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH);
      // --- 実行インジケータ
      m_progress_bar.LabelText("Get handles: "+string(symbols_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((m_handles[i]!=WRONG_VALUE)? "ok" : "wrong")+"...");
      m_progress_bar.Update(i,symbols_total);
      ::Sleep(5);
     }
// ---最初のチャートハンドルインデックスを保存
   m_current_handle_index=0;
  }

CProgram::GetIndicatorValues()メソッドを使用して、インジケータの値を取得します。そのアルゴリズムについて説明しましょう。まず、インジケータの値の配列のサイズをハンドルの配列のサイズと同じに設定します。メインサイクルでは、ハンドルの配列を通り、繰り返しごとに5回インジケータデータの取得を試みます。念のため、ハンドルの正確性をチェックし、それが以前に取得されていなかった場合は、再度取得を試みます。メインサイクルの最後に進捗バーを更新し、プログラムが現在どのステージにあるかを確認します。

class CProgram : public CWndEvents
  {
private:
   // --- インジケータ値
   double            m_values[];
   //---
private:
   // --- すべてのシンボルのインジケータ値を取得します
   void              GetIndicatorValues(void);
  };
//+------------------------------------------------------------------+
// |すべてのシンボルのインジケータ値を取得します。                         |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorValues(void)
  {
// --- サイズを設定する
   int handles_total=::ArraySize(m_handles);
   ::ArrayResize(m_values,handles_total);
// --- コンボボックスのドロップダウンリストから値を取得する
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
// --- リスト内のすべてのシンボルのインジケータデータを取得する
   for(int i=0; i<handles_total; i++)
     {
      // --- データの取得を5回試みる
      int attempts=0;
      int received=0;
      while(attempts<5)
        {
         // --- ハンドルが無効な場合は、再度取得を試みる
         if(m_handles[i]==WRONG_VALUE)
           {
            // --- インジケータハンドルを取得する
            m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH);
            continue;
           }
         // --- インジケータ値の取得を試みる
         double values[1];
         received=::CopyBuffer(m_handles[i],1,0,1,values);
         if(received>0)
           {
            // --- 値を保存する
            m_values[i]=values[0];
            break;
           }
         // ---カウンタを増やす
         attempts++;
         ::Sleep(100);
        }
      // --- 実行インジケータ
      m_progress_bar.LabelText("Get values: "+string(handles_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((received>0)? "ok" : "wrong")+"...");
      m_progress_bar.Update(i,handles_total);
      ::Sleep(5);
     }
  }

シンボルリストが形成され、インジケータのデータが受信された後、これらの配列の値をTradeタブの表に追加する必要があります。この課題は、CProgram::RebuildingTables()メソッドで解決することができます。シンボルの数は変更可能です。したがって、このメソッドが呼び出されるたびに、表は完全に再形成されます。

まず、バックアップを除くすべての行が削除されます。次に、シンボル数に則り再び行が表に追加されます。それからサイクルを回り以前に別々の配列で受信した値を追加します。値そのものに加えて、インジケータの値に応じてどのシグナルがすでに形成されているかを確認する為に、色でテキストを強調表示する必要があります。値がStochasticインジケーターの最低値以下の場合、BUYシグナルとして青色で強調表示し、最大値以上の値はSELLシグナルとして赤色で強調表示します。プログラムが進行に応じて、各反復で進行状況バーが更新されます。メソッドの最後に、表とスクロールバーを更新する必要があります。

//+------------------------------------------------------------------+
// | シンボルの表を再構築する                                           |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
// --- すべての行を削除する
   m_table_symb.DeleteAllRows();
// --- 行数を文字数で設定する
   int symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<symbols_total-1; i++)
      m_table_symb.AddRow(i);
// --- 最初の列に値を設定する
   uint rows_total=m_table_symb.RowsTotal();
   for(uint r=0; r<(uint)rows_total; r++)
     {
      // --- 値を設定する
      m_table_symb.SetValue(0,r,m_symbols[r]);
      m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2));
      // --- 色を設定する
      color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack;
      m_table_symb.TextColor(0,r,clr);
      m_table_symb.TextColor(1,r,clr);
      // --- 進行状況バーを更新する
      m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"...");
      m_progress_bar.Update(r,rows_total);
      ::Sleep(5);
     }
// --- 表を更新する
   m_table_symb.Update(true);
   m_table_symb.GetScrollVPointer().Update(true);
   m_table_symb.GetScrollHPointer().Update(true);
  }

上記のすべてのメソッドは、CProgram :: RequestData()メソッド内で呼び出されます。単一の引数を渡し、その値に応じて、コントロールの識別子(Requestボタン)をチェックします。このチェックの後、一時的に表を非表示にして進行状況バーを表示させます。次に、データを取得およびデータを表に挿入する為に、上記のすべてのメソッドが連続して呼び出されます。それから進行状況バーを非表示にし、コンボボックスからグラフまでの時間枠を設定し、最後の変更を表示させます。 

//+------------------------------------------------------------------+
// |データのリクエスト                                                 |
//+------------------------------------------------------------------+
bool CProgram::RequestData(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_request.Id())
      return(false);
// --- 表を非表示にする
   m_table_symb.Hide();
// --- 進行状況を表示する
   m_progress_bar.Show();
   m_chart.Redraw();
// --- チャートと表を初期化する
   GetSymbols();
   GetHandles();
   GetIndicatorValues();
   RebuildingTables();
// --- 進行を隠す
   m_progress_bar.Hide();
// --- コンボボックスリストから値を取得する
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
// --- インデックスでチャートのポインタを取得する
   m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
// --- 表を表示する
   m_table_symb.Show();
   m_chart.Redraw();
   return(true);
  }

オープンポジションのデータの取得

エキスパートアドバイザーがチャートにロードされたら、表内のPositionsタブにこの情報を表示する為のオープンポジションがあるかどうかをすぐに判断する必要があります。≪取引≫タブの≪ツール≫ウィンドウで、すべてのポジションのリストを参照することができます。シンボルで1つのポジションのみを閉じるには、表内のProfit列の十字をクリックする必要があります。シンボル上に複数のポジション(ヘッジアカウント上)があり、すべてを閉じる必要がある場合は、いくつかの手順が必要になります。グラフィカルインターフェースのポジションの表に、各シンボルにつき1行に、現在の結果、デポジット、および平均価格による総合的な情報があるようにします。さらに、指定したシンボルのすべてのポジションをワンクリックで一度に閉じる機能を追加します。 

まず、 CProgram::GetPositionsSymbols()メソッドを使用して、開いているポジションごとにシンボルのリストを取得します。ここに空の動的配列が渡され、シンボルが取得されます。それからサイクル内ですべてのオープンポジションを通過します。各反復で、ポジションシンボルの名前を取得し、 «,»セパレータを介して文字列変数に追加します。シンボル名を追加する前に、まず既にこの行にシンボル名がすでに入っていないかどうかを確認します。 

サイクルが終了し、文字列が形成された後この文字列の要素を引き渡した配列に取得し、受け取ったシンボルの数を返します。

//+------------------------------------------------------------------+
// | オープンポジションのシンボルを配列に取得                             |
//+------------------------------------------------------------------+
int CProgram::GetPositionsSymbols(string &symbols_name[])
  {
   string symbols="";
// --- 初回のサイクルを行い、オープンポジションのシンボルを取得します
   int positions_total=::PositionsTotal();
   for(int i=0; i<positions_total; i++)
     {
      // --- ポジションを選択してシンボルを取得します
      string position_symbol=::PositionGetSymbol(i);
      // --- シンボル名がある場合
      if(position_symbol=="")
         continue;
      // --- そのような行がまだない場合は、それを追加します
      if(::StringFind(symbols,position_symbol,0)==WRONG_VALUE)
         ::StringAdd(symbols,(symbols=="")? position_symbol : ","+position_symbol);
     }
// --- セパレータで文字列の要素を取得する
   ushort u_sep=::StringGetCharacter(",",0);
   int symbols_total=::StringSplit(symbols,u_sep,symbols_name);
// --- シンボル数を返します
   return(symbols_total);
  }

シンボルの配列ができたので、シンボル名を指定するだけで各ポジションごとのデータを取得できます。ポジション表のすべてのデータ列でインデックスを取得する方法を考えてみましょう。

指定されたシンボルのポジション数を取得するには、CProgram::PositionsTotal()メソッドを使用します。これは、サイクル内ですべてのポジションを通過し、メソッド引数で指定されたシンボルと一致するものだけを数えます。

//+------------------------------------------------------------------+
// | 指定されたプロパティを持つトポジションのトランザクション数             |
//+------------------------------------------------------------------+
int CProgram::PositionsTotal(const string symbol)
  {
// --- ポジションカウンター
   int pos_counter=0;
// ---指定されたプロパティを持つポジションがあるかどうかをチェックする
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      // --- ポジションを選択できない場合は、次に移動します
      if(symbol!=::PositionGetSymbol(i))
         continue;
      // --- カウンターを拡大する
      pos_counter++;
     }
// --- ポジション数を返します
   return(pos_counter);
  }

CProgram::PositionsVolumeTotal()メソッドを使用して、ポジションボリュームを取得することができます。ポジションボリュームを取得する必要があるシンボルの他にメソッドにこれらのタイプを渡すこともできます。しかし、ポジションタイプはこのメソッドでは必須引数です。デフォルト値はWRONG_VALUEです。タイプが指定されていない場合、このチェックは使用されず、このメソッドはすべてのポジションボリュームの合計を返します。 

//+------------------------------------------------------------------+
// | 指定されたプロパティを持つポジション総量                             |
//+------------------------------------------------------------------+
double CProgram::PositionsVolumeTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
// --- ボリュームカウンタ
   double volume_counter=0;
// ---指定されたプロパティを持つポジションがあるかどうかをチェックする
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      // --- ポジションを選択できない場合は、次に移動します
      if(symbol!=::PositionGetSymbol(i))
         continue;
      // --- タイプをチェックする必要がある場合
      if(type!=WRONG_VALUE)
        {
         // --- 型が一致しない場合は、次のポジションに移動します
         if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            continue;
        }
      // --- ボリュームを合計する
      volume_counter+=::PositionGetDouble(POSITION_VOLUME);
     }
// --- ボリュームを返す
   return(volume_counter);
  }

CProgram::PositionsFloatingProfitTotal()メソッドによって、指定したシンボルのポジションの流動利益合計を取得できます。計算は、ポジションの累積スワップを考慮に入れます。ここでは、追加のオプション引数として流動利益を取得するポジションのタイプを指定することもできます。このようにすることで、メソッドはユニバーサルなものになります。 

//+------------------------------------------------------------------+
// | 指定されたプロパティをポジションの流動利益の合計                      |
//+------------------------------------------------------------------+
double CProgram::PositionsFloatingProfitTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
// --- 現在の利益カウンター
   double profit_counter=0.0;
// ---指定されたプロパティを持つポジションがあるかどうかをチェックする
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      // --- ポジションを選択できない場合は、次に移動します
      if(symbol!=::PositionGetSymbol(i))
         continue;
      // --- タイプをチェックする必要がある場合
      if(type!=WRONG_VALUE)
        {
         // --- 型が一致しない場合は、次のポジションに移動します
         if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            continue;
        }
      // --- 現在の利益+累積スワップの合計
      profit_counter+=::PositionGetDouble(POSITION_PROFIT)+::PositionGetDouble(POSITION_SWAP);
     }
// --- 結果を返す
   return(profit_counter);
  }

CProgram::PositionAveragePrice()メソッドで平均価格を計算します。シンボルの各ポジションのサイクルで、価格とボリュームを取得します。次に、 これらの指標の積と別々にポジションボリュームを合計します。サイクル終了後に、指定したシンボルのボリュームの平均価格を取得する為に、価格の積とボリュームの合計をボリュームの合計で除算する必要があります。表示されたメソッドを返すのはこの値です

//+------------------------------------------------------------------+
// | ポジションの平均価格                                              |
//+------------------------------------------------------------------+
double CProgram::PositionAveragePrice(const string symbol)
  {
// --- 平均価格を計算するために
   double sum_mult    =0.0;
   double sum_volumes =0.0;
// ---指定されたプロパティを持つポジションがあるかどうかをチェックする
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      // --- ポジションを選択できない場合は、次に移動します
      if(symbol!=::PositionGetSymbol(i))
         continue;
      // --- ポジションの価格と量を取得する
      double pos_price  =::PositionGetDouble(POSITION_PRICE_OPEN);
      double pos_volume =::PositionGetDouble(POSITION_VOLUME);
      // --- 中間インジケータを合計する
      sum_mult+=(pos_price*pos_volume);
      sum_volumes+=pos_volume;
     }
// --- ゼロ除算を予防する
   if(sum_volumes<=0)
      return(0.0);
// --- 平均価格を返す
   return(::NormalizeDouble(sum_mult/sum_volumes,(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }

デポジットの金額を考えてみましょう。これを得るには、CProgram::DepositLoad()メソッドを呼び出す必要があります。渡された引数に応じて、アカウントの通貨とパーセンテージで異なる表示で値を取得することができます。さらに、すべての未決済ポジション、または指定されたシンボルについてのみ、デポジットの総額を取得することができます。 

このメソッドには4つの引数があり、そのうちの3つはオプションです。最初の引数がfalse値を持っている場合、メソッドはデポジット通貨の値を返します。true値が引き渡された場合、利用可能な資金に対するパーセント値で値を返します。 

指定したシンボルで現在のデポジットの額を取得する必要があり、アカウントの通貨がシンボルの基本通貨と異なる場合は、計算時にポジションの価格が必要になります。シンボル上に複数のポジションがある場合は、平均価格を引き渡す必要があります。 

//+------------------------------------------------------------------+
// | デポジットをロードする                                             |
//+------------------------------------------------------------------+
double CProgram::DepositLoad(const bool percent_mode,const double price=0.0,const string symbol="",const double volume=0.0)
  {
// --- デポジットロードの現在の値を計算する
   double margin=0.0;
// --- アカウントの総ロード額
   if(symbol=="" || volume==0.0)
      margin=::AccountInfoDouble(ACCOUNT_MARGIN);
// --- 指定したシンボルごとのロード
   else
     {
      // --- マージンを計算するためのデータを取得する
      double leverage         =((double)::AccountInfoInteger(ACCOUNT_LEVERAGE)==0)? 1 : (double)::AccountInfoInteger(ACCOUNT_LEVERAGE);
      double contract_size    =::SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);
      string account_currency =::AccountInfoString(ACCOUNT_CURRENCY);
      string base_currency    =::SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);
      // --- 取引口座の通貨がシンボルの基本通貨と同じ場合
      if(account_currency==base_currency)
         margin=(volume*contract_size)/leverage;
      else
         margin=(volume*contract_size)/leverage*price;
     }
// --- 現在の資金を取得する
   double equity=(::AccountInfoDouble(ACCOUNT_EQUITY)==0)? 1 : ::AccountInfoDouble(ACCOUNT_EQUITY);
// --- 現在のデポジット額を返す
   return((!percent_mode)? margin : (margin/equity)*100);
  }

指標を取得するためのすべてのメソッドは、CProgram::SetValuesToPositionsTable()メソッドを呼び出すことで表に追加されます。このメソッドに、関心のあるシンボルの配列を引き渡す必要があります。まず、引き渡された配列が表の行数以上であることを確認します。次に、表内のすべての行のサイクルの中で、指標を順番に受け取り、それで表のセルを埋めます。値そのものに加えて、テキストの色も設定します。緑は正、赤は負、灰はゼロです。デポジットのダウンロードはスラッシュ(『/』)で各シンボルごとに金額とパーセントで表示されることにご注意ください。 

//+------------------------------------------------------------------+
// | ポジション表に値を設定する                                         |
//+------------------------------------------------------------------+
void CProgram::SetValuesToPositionsTable(string &symbols_name[])
  {
// --- 範囲からの超越をチェックする
   uint symbols_total =::ArraySize(symbols_name);
   uint rows_total    =m_table_positions.RowsTotal();
   if(symbols_total<rows_total)
      return;
// --- 指標を表に取得します
   for(uint r=0; r<rows_total; r++)
     {
      int    positions_total =PositionsTotal(symbols_name[r]);
      double pos_volume      =PositionsVolumeTotal(symbols_name[r]);
      double buy_volume      =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_BUY);
      double sell_volume     =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_SELL);
      double pos_profit      =PositionsFloatingProfitTotal(symbols_name[r]);
      double buy_profit      =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_BUY);
      double sell_profit     =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_SELL);
      double average_price   =PositionAveragePrice(symbols_name[r]);
      string deposit_load    =::DoubleToString(DepositLoad(false,average_price,symbols_name[r],pos_volume),2)+"/"+
                              ::DoubleToString(DepositLoad(true,average_price,symbols_name[r],pos_volume),2)+"%";
      // --- 値を設定する
      m_table_positions.SetValue(0,r,symbols_name[r]);
      m_table_positions.SetValue(1,r,(string)positions_total);
      m_table_positions.SetValue(2,r,::DoubleToString(pos_volume,2));
      m_table_positions.SetValue(3,r,::DoubleToString(buy_volume,2));
      m_table_positions.SetValue(4,r,::DoubleToString(sell_volume,2));
      m_table_positions.SetValue(5,r,::DoubleToString(pos_profit,2));
      m_table_positions.SetValue(6,r,::DoubleToString(buy_profit,2));
      m_table_positions.SetValue(7,r,::DoubleToString(sell_profit,2));
      m_table_positions.SetValue(8,r,deposit_load);
      m_table_positions.SetValue(9,r,::DoubleToString(average_price,(int)::SymbolInfoInteger(symbols_name[r],SYMBOL_DIGITS)));
      // --- 色を設定する
      m_table_positions.TextColor(3,r,(buy_volume>0)? clrBlack : clrLightGray);
      m_table_positions.TextColor(4,r,(sell_volume>0)? clrBlack : clrLightGray);
      m_table_positions.TextColor(5,r,(pos_profit!=0)? (pos_profit>0)? clrGreen : clrRed : clrLightGray);
      m_table_positions.TextColor(6,r,(buy_profit!=0)? (buy_profit>0)? clrGreen : clrRed : clrLightGray);
      m_table_positions.TextColor(7,r,(sell_profit!=0)?(sell_profit>0)? clrGreen : clrRed : clrLightGray);
     }
  }

変更後のポジション表を更新するには、プログラム内のいくつかの場所で呼び出す必要があるため、別のメソッドを作成します。

//+------------------------------------------------------------------+
// | ポジション表を更新します。                                         |
//+------------------------------------------------------------------+
void CProgram::UpdatePositionsTable(void)
  {
// --- 表を更新する
   m_table_positions.Update(true);
   m_table_positions.GetScrollVPointer().Update(true);
   m_table_positions.GetScrollHPointer().Update(true);
  }

CProgram::InitializePositionsTable()メソッドでポジション表を初期化します。このセクションで説明したすべてのメソッドを呼び出します。まず、オープンポジションのシンボルを配列に取得します。次に、表を準備する必要があるので、すべての行を削除して新たに取得したシンボルの配列数を追加します。オープンポジションがある場合は、まず最初の列のセルをボタンとして指定する必要があります。これを行うには、 適切なタイプを設定し(CELL_BUTTON)、画像を追加する必要があります。その後、セルに値が設定され、表が更新されます。

//+------------------------------------------------------------------+
// | ポジション表を初期化する                                           |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\close_black.bmp"
//---
void CProgram::InitializePositionsTable(void)
  {
// ---オープンポジションのシンボルを取得する
   string symbols_name[];
   int symbols_total=GetPositionsSymbols(symbols_name);
// --- すべての行を削除する
   m_table_positions.DeleteAllRows();
// --- 行数を文字数で設定する
   for(int i=0; i<symbols_total-1; i++)
      m_table_positions.AddRow(i);
// --- ポジションがある場合
   if(symbols_total>0)
     {
      // --- ボタンのイメージ配列
      string button_images[1]={"Images\\EasyAndFastGUI\\Controls\\close_black.bmp"};
      // --- 3番目の列に値を設定する
      for(uint r=0; r<(uint)symbols_total; r++)
        {
         // --- タイプとイメージを設定する
         m_table_positions.CellType(0,r,CELL_BUTTON);
         m_table_positions.SetImages(0,r,button_images);
        }
      // --- 表に値を設定する
      SetValuesToPositionsTable(symbols_name);
     }
// --- 表を更新する
   UpdatePositionsTable();
  }

データで表を初期化する

GUIが作成された直後に、シンボル表とポジション表を初期化します。イベントハンドラのON_END_CREATE_GUIユーザーイベントで形成が終了したことを知ることができます。シンボル表を初期化するには、CProgram::RequestData()メソッド(これまでに紹介したもの)を呼び出す必要があります。メソッドが正常に動作するためには、ボタン要素識別子(リクエスト)を引き渡す必要があります。

//+------------------------------------------------------------------+
// |イベントハンドラ                                     |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
// --- GUI作成イベント
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
     {
      // --- データリクエスト
      RequestData(m_request.Id());
      // --- ポジション表を初期化する
      InitializePositionsTable();
      return;
     }
  }

プログラムのチャートへのロード後、シンボル表は次のようになります。

 図3  - 初期化されたシンボル表。

図 1. 初期化されたシンボル表。

アカウントポジションにプログラムがロードされる前に、既に開いているポジションがあった場合、ポジション表は次のようになります。

 図4  - 初期化されたポジション表。

図2. 初期化されたポジション表。

リアルタイムで表を更新する

価格は常に変動するため、取引セッション中に表のデータを常に再計算する必要があります。プログラムタイマーで一定の間隔で表を更新します。異なる間隔で要素を更新する為に、CTimeCounter型のオブジェクトを使用することができます。このクラスはEasyAndFastライブラリアセンブリ内にあります。それをプロジェクトで使用するには、 ファイルとその内容を接続すれば十分です

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#include <EasyAndFastGUI\TimeCounter.mqh>
...

私たちのエキスパートアドバイザーでは、ステータス行と表のデータを更新するために3つの時間カウンターが必要です。 

カウンタを設定するには、CTimeCounter型のオブジェクトを宣言し、コンストラクタでパラメータを設定する必要があります(以下のリストをご参照ください)。第1のパラメータは、タイマー周波数で、第2のパラメータはCTimeCounter::CheckTimeCounter()メソッドがtrueを返したあとの時間間隔です。この後、カウンタはリセットされ、新たに累積を開始します。

//+------------------------------------------------------------------+
// |アプリケーションを作成するクラス                                     |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
...
   // --- タイムカウンタ
   CTimeCounter      m_counter1;
   CTimeCounter      m_counter2;
   CTimeCounter      m_counter3;
...
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
// --- タイムカウンタのパラメータの設定
   m_counter1.SetParameters(16,500);
   m_counter2.SetParameters(16,5000);
   m_counter3.SetParameters(16,1000);
...
  }

プログラムタイマーの最初のカウンターのブロック内のステータスラインを更新するには、以下のコードを追加します。変更を表示するには、各項目を個別に更新することを忘れないようにしてください。

//+------------------------------------------------------------------+
// | タイマー                                                         |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
...
// --- ステータスバーの項目を更新する
   if(m_counter1.CheckTimeCounter())
     {
      // --- 値を設定する
      m_status_bar.SetValue(1,"Deposit load: "+::DoubleToString(DepositLoad(false),2)+"/"+::DoubleToString(DepositLoad(true),2)+"%");
      m_status_bar.SetValue(2,::TimeToString(::TimeTradeServer(),TIME_DATE|TIME_SECONDS));
      // --- 項目を更新する
      m_status_bar.GetItemPointer(1).Update(true);
      m_status_bar.GetItemPointer(2).Update(true);
     }
...
  }

インジケータ値のみを置き換える必要のあるテーブルの更新を高速化するため、別のCProgram::UpdateSymbolsTable()メソッドを使用します。これを呼び出す前に、まずインジケーター値の配列を更新する必要があります。それから、CProgram()UpdateSymbolsTableメソッドを呼び出します。ここで、反復ごとに配列の境界の超越がチェックされます。テストが終了すると、表の2番目の列のセルが更新され、テキストの色が修正されます。データの取得および表の初期化プロセスが進行状況バーに表示されます。

//+------------------------------------------------------------------+
// | シンボル表を更新します。                                           |
//+------------------------------------------------------------------+
void CProgram::UpdateSymbolsTable(void)
  {
   uint values_total=::ArraySize(m_values);
// --- シンボル表の値を設定する
   uint rows_total=m_table_symb.RowsTotal();
   for(uint r=0; r<(uint)rows_total; r++)
     {
      // --- 配列を超えた場合はサイクルを停止する
      if(r>values_total-1 || values_total<1)
         break;
      // --- 値を設定する
      m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2));
      // --- 色を設定する
      color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack;
      m_table_symb.TextColor(0,r,clr,true);
      m_table_symb.TextColor(1,r,clr,true);
      // --- 進行状況バーを更新する
      m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"...");
      m_progress_bar.Update(r,rows_total);
      ::Sleep(5);
     }
// --- 表を更新する
   m_table_symb.Update();
  }

シンボル表を更新するための第2の時間カウンタのブロックは以下の通りです。このように、5秒ごとにプログラムはすべてのシンボルのインジケータの現在の値を受け取り、表を更新します。

void CProgram::OnTimerEvent(void)
  {
...
// --- シンボル表を更新する
   if(m_counter2.CheckTimeCounter())
     {
      // --- 進行状況を表示する
      m_progress_bar.Show();
      m_chart.Redraw();
      // --- 表の値を更新する
      GetIndicatorValues();
      UpdateSymbolsTable();
      // --- 進行を隠す
      m_progress_bar.Hide();
      m_chart.Redraw();
     }
...
  }

タイマー内のポジション表を更新するには、まず、オープンポジションのシンボルの配列を取得します。次に、実際のデータで表を更新します。更新前と同じ列と方向で並べ替えます。表に適用して変更を表示します。

void CProgram::OnTimerEvent(void)
  {
...
// --- ポジション表を更新する
   if(m_counter3.CheckTimeCounter())
     {
      // ---オープンポジションのシンボルを取得する
      string symbols_name[];
      int symbols_total=GetPositionsSymbols(symbols_name);
      // --- 表の値を更新する
      SetValuesToPositionsTable(symbols_name);
      // ---更新前にユーザーが既に行っているかどうかをソートする
      m_table_positions.SortData((uint)m_table_positions.IsSortedColumnIndex(),m_table_positions.IsSortDirection());
      // --- 表を更新する
      UpdatePositionsTable();
     }
  }

コントロール要素のイベント処理

このセクションでは、ユーザーとエキスパートアドバイザーのGUIとの作業時に生成されるイベントを処理するメソッドを見ていきます。すでにインジケータに関するシンボルとデータを取得するCProgram::RequestData()メソッドを紹介しました。初期化が初回でない場合、プログラムの実行中にRequestボタンを押すとメソッドが呼び出されます。ボタン押下時に識別子付きカスタムイベントをもつON_CLICK_BUTTONが生成されます。

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
// --- ボタンを押すイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      // --- データリクエスト
      if(RequestData(lparam))
         return;
      //---
      return;
     }
...
  }

下のgifで次のことがわかります。USDを含むforex-symbolsのリストが表に生成されます。次に、EURを含むシンボルリストを素早く作成します。これを行うには、 入力フィールドのSymbols filter『EUR』と記入し、Requestボタンをクリックします。サーバ上のUSDとEURの通貨を持つシンボルを全て表示する場合は、これらの通貨をコンマで入力する必要があります(「USD、EUR」)。

 図5  - 外国為替記号のリストを作成する。

図3.外国為替記号リストを作成する。

Timeframesコンボボックスで指定した期間でインジケータハンドルの取得と外国為替記号のリストの作成が行われます。ドロップダウンリストで別の期間を選択する場合は、新しいハンドルを取得して表の値を更新する必要があります。このためには、CProgram::ChangePeriod()メソッドが必要です。コンボボックス識別子が来たら、まずグラフィックスオブジェクトの時間枠を更新します。それから、表内のすべてのシンボルのハンドルとインジケータデータを取得し、その後、変更を表示するように更新します。 

//+------------------------------------------------------------------+
// |タイムフレームの変更                                               |
//+------------------------------------------------------------------+
bool CProgram::ChangePeriod(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_timeframes.Id())
      return(false);
// --- コンボボックスリストから値を取得する
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
// --- インデックスでチャートのポインタを取得する
   m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
// --- 進行状況を表示する
   m_progress_bar.Show();
   m_chart.Redraw();
// --- インジケータのハンドルとデータを取得します
   GetHandles();
   GetIndicatorValues();
// --- 表を更新する
   UpdateSymbolsTable();
// --- 進行を隠す
   m_progress_bar.Hide();
   m_chart.Redraw();
   return(true);
  }

ドロップダウンリストからアイテムを選択すると、識別子付きカスタムイベントON_CLICK_COMBOBOX_ITEMが生成されます。

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
// ---コンボボックスで項目を選択するイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      // --- 時間枠を変更する
      if(ChangePeriod(lparam))
         return;
      //---
      return;
     }
...
  }

時間枠の変更と新しいデータの表示は次のようになります。

 図6  - 時間枠の変更。

図4.時間枠の変更。

次に、グラフィックスオブジェクトのシンボルをすばやく変更する方法について紹介します。シンボル表では、最初の列には既にシンボル名があります。したがって、表内の行を選択するだけで、それらの間を切り替えることができます。任意の行をクリックすると、CProgram::ChangeSymbol()メソッドが呼び出されます。ここで、シンボル表の識別子が最初にチェックされます。それから、2回目のクリックで強調表示が解除されるため、表内の行が強調表示されているかどうかを確認する必要があります。これらのチェックが終わったら、次にハンドルのインデックスとして、選択した行のインデックスを保存する必要があります。これによってインジケータをチャート上に設定することができるようになります。(詳細は記事を読んでください)。

選択した行のインデックスによって表の第1列からシンボルを受け取ったら、 それをグラフィックスオブジェクトに設定します。追加情報として、シンボルのフル説明をステータス行の最初の項目に出力します。表の行を選択解除すると、テキストはデフォルト値に設定されます。

//+------------------------------------------------------------------+
// | 記号を変更する                                                    |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_table_symb.Id())
      return(false);
// --- 行が選択されていない場合は終了する
   if(m_table_symb.SelectedItem()==WRONG_VALUE)
     {
      // --- ステータスバーにシンボルの説明を表示する
      m_status_bar.SetValue(0,"For Help, press F1");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
// --- ハンドルのインデックスを保存する
   m_current_handle_index=m_table_symb.SelectedItem();
// --- シンボルを取得する
   string symbol=m_table_symb.GetValue(0,m_current_handle_index);
// --- チャートを更新する
   m_sub_chart1.GetSubChartPointer(0).Symbol(symbol);
   m_sub_chart1.ResetCharts();
// --- ステータスバーにシンボルの説明を表示する
   m_status_bar.SetValue(0,::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
   m_chart.Redraw();
   return(true);
  }

表内の行を選択すると、 識別子付きカスタムイベントON_CLICK_LIST_ITEMが生成されます。シンボルを『Up』、『Down』、『Home』、『End』キーと切り替えることができます。この場合、 イベント CHARTEVENT_KEYDOWNが生成されます。この処理の為のメソッドは前の記事で紹介しているので、ここではそれには触れません。

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
// --- リスト/表内の項目の選択イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      // --- シンボルを変更する
      if(ChangeSymbol(lparam))
         return;
      //---
      return;
     }
// --- キーを押す
   if(id==CHARTEVENT_KEYDOWN)
     {
      // --- キーを使用して結果を選択する
      if(SelectingResultsUsingKeys(lparam))
         return;
      //---
      return;
     }
...
  }

これらのイベントを処理した結果、次のようなものが表示されます。

 図7  - シンボルの切り替え。

図5. シンボルの切り替え。

場合によっては、シグナルを受信するチャート上のインジケータを表示する必要があります。インジケータの表示を有効にするには、Show indicatorチェックボックスをオンにする必要があります。それとの相互作用は、CProgram::ShowIndicator()メソッドが行います。ここでは、要素の識別子とハンドル配列の範囲外のチェックもする必要があります。インジケータをグラフオブジェクトに追加または削除するには、 このチャート識別子が必要です。次にチェックボックスがオンの場合は、インジケータをチャートに追加する必要があります。インジケータは常に1つであるため、サブウィンドウの番号を1に設定します。より複雑な場合には、チャート上のインジケータ数を決定する必要があります。 

//+------------------------------------------------------------------+
// | インジケータの可視性                                              |
//+------------------------------------------------------------------+
bool CProgram::ShowIndicator(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_show_indicator.Id())
      return(false);
// --- 配列の境界の超越をチェックする
   int handles_total=::ArraySize(m_handles);
   if(m_current_handle_index<0 || m_current_handle_index>handles_total-1)
      return(true);
// --- チャートの識別子を取得する
   long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID);
// --- インジケータのサブウィンドウの番号
   int subwindow =1;
// --- インデックスでチャートのポインタを取得する
   if(m_show_indicator.IsPressed())
     {
      // --- チャートにインジケータを追加する
      ::ChartIndicatorAdd(sub_chart_id,subwindow,m_handles[m_current_handle_index]);
     }
   else
     {
      // --- チャートからインジケータを削除する
      ::ChartIndicatorDelete(sub_chart_id,subwindow,ChartIndicatorName(sub_chart_id,subwindow,0));
     }
// --- チャートを更新する
   m_chart.Redraw();
   return(true);
  }

チェックボックスとの動作時にカスタムイベントON_CLICK_CHECKBOXが生成されます。

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
// --- 『チェックボックス』要素のクリックイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
     {
      // --- 『Show indicator』チェックボックスをオンにした場合
      if(ShowIndicator(lparam))
         return;
      //---
      return;
     }
  }

これはこのようになります。

 図8  - インジケータの表示。

図6. インジケーターの表示。

エキスパートアドバイザーのGUIのインジケータには、関連するコントロール要素がさらに2つあります。これらは、インジケータレベルのデジタル入力フィールド(StochasticUp levelDown level)です。デフォルトでは、80と20に設定されています。各シンボルのインジケータの値がこれらの上限を上回ったり下回ったりすると、シンボル表のセル内のテキストは、上のレベルでは黒から青に変わり、下のレベルでは赤に変わります。これらの入力フィールドの値を変更すると、次回の更新時(5秒ごと)にカラー表示が変更されます。 

値を80/20から90/10に変更して戻したときの動作は次のとおりです。

 図9  - インジケータのシグナルレベルの変更

図7. インジケータのシグナルレベルの変更

いくつかのコントロール要素は、チャートのプロパティを操作するように設計されています。これは、

Date scalePrice scaleのチェックボックスからイベントを処理するメソッドと非常に似ています。そしてチェックボックスの状態に応じて、チャートの対応するプロパティがオンまたはオフになりますCStandardChart::ResetCharts()メソッドは、チャートを最後へシフトします

//+------------------------------------------------------------------+
// |タイムラインの可視性                                               |
//+------------------------------------------------------------------+
bool CProgram::DateScale(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_date_scale.Id())
      return(false);
// --- インデックスでチャートのポインタを取得する
   m_sub_chart1.GetSubChartPointer(0).DateScale(m_date_scale.IsPressed());
   m_sub_chart1.ResetCharts();
// --- チャートを更新する
   m_chart.Redraw();
   return(true);
  }
//+------------------------------------------------------------------+
// | 価格スケールの可視性                                               |
//+------------------------------------------------------------------+
bool CProgram::PriceScale(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_price_scale.Id())
      return(false);
// --- インデックスでチャートのポインタを取得する
   m_sub_chart1.GetSubChartPointer(0).PriceScale(m_price_scale.IsPressed());
   m_sub_chart1.ResetCharts();
// --- チャートを更新する
   m_chart.Redraw();
   return(true);
  }

チャートスケールを制御するには、CProgram::ChartScale()メソッドを使用します。ここでは、 入力フィールドの値が変更された場合、その値はチャートに割り当てられます

//+------------------------------------------------------------------+
// | チャートスケール                                                  |
//+------------------------------------------------------------------+
bool CProgram::ChartScale(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_chart_scale.Id())
      return(false);
// --- スケールを設定する
   if((int)m_chart_scale.GetValue()!=m_sub_chart1.GetSubChartPointer(0).Scale())
      m_sub_chart1.GetSubChartPointer(0).Scale((int)m_chart_scale.GetValue());
// --- 更新する
   m_chart.Redraw();
   return(true);
  }

Chart scale入力フィールドの値の変更は、ON_CLICK_BUTTONON_END_EDITの識別子を持つカスタムイベントの到着で処理されます。

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
// --- ボタンを押すイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      // --- チャートスケール
      if(ChartScale(lparam))
         return;
      //---
      return;
     }
// --- 入力フィールドの値変更を終了するイベント
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      // --- チャートスケール
      if(ChartScale(lparam))
         return;
      //---
      return;
     }
  }

CProgram::ChartShift()メソッドのコードは、右のインデントのプロットに含めるためのものです。ここには、要素の識別子をチェックした後、最初にチャートの識別子を取得し、それからそれをアクセスキーとして使用して、 インデントを設定することができますCHART_SHIFT)。

//+------------------------------------------------------------------+
// | チャートのシフト                                                  |
//+------------------------------------------------------------------+
bool CProgram::ChartShift(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_chart_shift.Id())
      return(false);
// --- チャートの識別子を取得する
   long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID);
// --- チャートの右側にインデントを設定する
   ::ChartSetInteger(sub_chart_id,CHART_SHIFT,true);
   m_sub_chart1.ResetCharts();
   return(true);
  }

このようになります。

 図10  - チャートのプロパティを管理する。

図8. チャートのプロパティを管理する。

取引操作実行メソッド

エキスパートアドバイザーのグラフィカルインタフェースと取引方メソッドを簡単かつ迅速にリンクする方法の例を紹介します。エキスパートアドバイザーは、データを視覚化するだけでなく、取引操作も行います。すべてが1か所にあるときに作業でき、チャートや必要に応じて取引もすばやく切り替えることができるのが便利です。取引操作の例として、標準ライブラリの機能を使用します。しかし、他の取引ライブラリと接続することもできます。 

Trade.mqhプロジェクトファイルにCTradeクラスに接続し、このクラスのインスタンスを宣言します

// --- 取引操作のクラス
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
// |アプリケーションを作成するクラス                                     |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   // --- 取引操作
   CTrade            m_trade;
  };

プログラムが各取引の結果を待たないようにするために、非同期トランザクションモードを設定します。さらに、 最大許容スリップを設定します。つまり、取引は、取引操作における指定価格からの逸脱に対して行われます。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
...
   m_trade.SetAsyncMode(true);
   m_trade.SetDeviationInPoints(INT_MAX);
  }

BuySellボタンをクリックすると、同様のCProgram::OnBuy()とCProgram::OnSell()メソッドで処理されます。Lot入力フィールドから取引の為のボリュームを取得します取引するシンボルは、オブジェクトチャートで選択します。これは、取引操作を完了するために必要な最小値です。CTradeクラスにはCTrade::Buy()とCTrade::Sell()メソッドがあり、呼出時にこれらの引数のうちの2つだけを渡すことができます。 

//+------------------------------------------------------------------+
// | Buy                                                             |
//+------------------------------------------------------------------+
bool CProgram::OnBuy(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_buy.Id())
      return(false);
// --- ポジションを開くためのシンボルとボリューム
   double lot    =::NormalizeDouble((double)m_lot.GetValue(),2);
   string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol();
// --- ポジションを開く
   m_trade.Buy(lot,symbol);
   return(true);
  }
//+------------------------------------------------------------------+
// | Sell                                                            |
//+------------------------------------------------------------------+
bool CProgram::OnSell(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_sell.Id())
      return(false);
// --- ポジションを開くためのシンボルとボリューム
   double lot    =::NormalizeDouble((double)m_lot.GetValue(),2);
   string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol();
// --- ポジションを開く
   m_trade.Sell(lot,symbol);
   return(true);
  }

一度にすべてのポジションを閉じる、または指定したシンボルだけを閉じるには、別のメソッドを記述する必要があります。つまりCTradeクラス内にはこのようなものはありません。シンボルがメソッドに渡された場合(必須ではないパラメータ)、このシンボル上のポジションのみが閉じられます。シンボルが指定されていない場合は、すべてのポジションが閉じられます。

//+------------------------------------------------------------------+
// | すべてのポジションを閉じる                                         |
//+------------------------------------------------------------------+
bool CProgram::CloseAllPosition(const string symbol="")
  {
// ---指定されたプロパティを持つポジションがあるかどうかをチェックする
   int total=::PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      // --- ポジションを選択する
      string pos_symbol=::PositionGetSymbol(i);
      // --- シンボルで閉じるものが指定されている場合
      if(symbol!="")
         if(symbol!=pos_symbol)
            continue;
      // --- チケットを取得する
      ulong position_ticket=::PositionGetInteger(POSITION_TICKET);
      // --- 最後のエラーをリセットする
      ::ResetLastError();
      // --- ポジションが閉じなかった場合は、それに関するメッセージを表示する
      if(!m_trade.PositionClose(position_ticket))
         ::Print(__FUNCTION__,": > An error occurred when closing a position: ",::GetLastError());
     }
//---
   return(true);
  }

すべてのポジションの決済は、Close all positionsボタンに関連付けられています。これを押下すると、CProgram::OnCloseAllPositions()メソッド内で処理されます。ボタンが誤って押されるのを防ぐために、 操作確認のためのダイアログが開きます。

//+------------------------------------------------------------------+
// | すべてのポジションを閉じる                                         |
//+------------------------------------------------------------------+
bool CProgram::OnCloseAllPositions(const long id)
  {
// --- 要素の識別子を確認する
   if(id!=m_close_all.Id())
      return(false);
// --- ダイアログボックス
   int mb_id=::MessageBox("Are you sure you want to close \nall positions?","Close positions",MB_YESNO|MB_ICONWARNING);
// --- ポジションを閉じる
   if(mb_id==IDYES)
      CloseAllPosition();
//---
   return(true);
  }

このようになります。

 図11  - すべてのポジションを閉じる。

図9. すべてのポジションを閉じる。

Positionsタブ内の指定したシンボルのポジションを閉じることができます。ポジション表の最初の列のセルに十字のボタンが追加されています。これを使用してシンボルのすべてのポジションをすぐに閉じることができ、シンボルのデータはこの行に表示されます。セル内のボタンを押すと、識別子を持つユーザイベントON_CLICK_BUTTONが生成されます。しかし、CTable型の要素内にはスクロールバーがあり、そのボタンは同じイベントを生成し、要素の識別子も一致します。したがって、イベントの文字列パラメータ(sparam)を追跡し、誤って要素の他のボタンのクリックを処理しないようにします。文字列パラメータでは、押された要素のタイプを指定します。スクロールバーにはこの『scroll』値があります。。このような値を持つイベントが来た場合、プログラムはメソッドを終了します。その後、まだ開いているポジションがあるかどうかを確認する必要があります。

すべてのチェックが完了した場合は、文字列パラメータの説明から表の最初の列にあるシンボルを抽出する必要があります。ここでも、偶発的なボタンのクリックを除外するために、アクションを確認するためのダイアログボックスが最初に表示されます。はいボタンをクリックすると、指定したシンボルのポジションのみを閉じます。。 

//+------------------------------------------------------------------+
// | 指定したシンボルのすべてのポジションを閉じる                         |
//+------------------------------------------------------------------+
bool CProgram::OnCloseSymbolPositions(const long id,const string desc)
  {
// --- 要素の識別子を確認する
   if(id!=m_table_positions.Id())
      return(false);
// --- スクロールバーボタンを押している場合は終了する
   if(::StringFind(desc,"scroll",0)!=WRONG_VALUE)
      return(false);
// --- ポジションがない場合は終了する
   if(::PositionsTotal()<1)
      return(true);
// --- 文字列からデータを抽出する
   string str_elements[];
   ushort sep=::StringGetCharacter("_",0);
   ::StringSplit(desc,sep,str_elements);
// --- インデックスとシンボルを取得する
   int    row_index =(int)str_elements[1];
   string symbol    =m_table_positions.GetValue(0,row_index);
// --- ダイアログボックス
   int mb_id=::MessageBox("Are you sure you want to close \nall positions on symbol "+symbol+"?","Close positions",MB_YESNO|MB_ICONWARNING);
// --- 指定したシンボルのすべてのポジションを閉じる
   if(mb_id==IDYES)
      CloseAllPosition(symbol);
//---
   return(true);
  }

このようになります。

 図11  - 指定したシンボルのすべてのポジションを閉じる。

図10. 指定したシンボルのすべてのポジションを閉じる。

上記のすべての取引操作は、ON_CLICK_BUTTONイベントの発生時に処理されます。

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
// --- ボタンを押すイベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Buy
      if(OnBuy(lparam))
         return;
      //--- Sell
      if(OnSell(lparam))

         return;
      // --- すべてのポジションを閉じる
      if(OnCloseAllPositions(lparam))
         return;
      // --- 指定したシンボルのすべてのポジションを閉じる
      if(OnCloseSymbolPositions(lparam,sparam))
         return;
      //---
      return;
     }
...
  }

各取引はポジション表に反映される必要があり、これを行うには、取引イベントと取引口座の取引履歴を追跡する必要があります。トランザクションの数が変更された場合は、表を新たに作成します。履歴が変更されたかどうかを確認するには、CProgram::IsLastDealTicket()メソッドが使用されます。チェックのたびに、最後のトランザクションの時間とチケットを保存する必要があります。毎回取引の全履歴をリクエストしないようにするために時間を記録しますチケットで履歴のトランザクション数が変更されたかどうかをチェックします。トランザクションはいくつかの取引イベントを開始するので、この方法はtrueを一度だけ返します。

class CProgram : public CWndEvents
  {
private:
   // --- 最後にチェックされたトランザクションの時間とチケット
   datetime          m_last_deal_time;
   ulong             m_last_deal_ticket;
   //---
private:
   // --- 履歴から新しい取引を確認する
   bool              IsLastDealTicket(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deal_time(NULL),
                           m_last_deal_ticket(WRONG_VALUE)
  {
...
  }
//+------------------------------------------------------------------+
// | 履歴の中の新しい取引をチェックする                                  |
//+------------------------------------------------------------------+
bool CProgram::IsLastDealTicket(void)
  {
// --- 履歴が取得されなかった場合は終了する
   if(!::HistorySelect(m_last_deal_time,UINT_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);
        }
     }
// --- 別のシンボルのチケット
   return(false);
  }

CProgram::IsLastDealTicket()メソッドがイベントハンドラで呼び出されます。履歴が変わった場合は、ポジション表が新たに形成されます。

//+------------------------------------------------------------------+
// | 取引操作イベント                                                  |
//+------------------------------------------------------------------+
void CProgram::OnTradeEvent(void)
  {
// --- 新しいトランザクション
   if(IsLastDealTicket())
     {
      // --- ポジション表を初期化する
      InitializePositionsTable();
     }
  }

このようになります。

 図12  - ポジションがシンボルによって閉じられたときの表の作成。

図11. ポジションがシンボルによって閉じられたときの表の作成。

まとめ

この記事では、複雑なレベルのプログラム用のグラフィカルインターフェイスを作成することがいかに簡単かをご紹介しました。あなたはこのプログラムを開発して、あなた自身の目的のためにそれを使い続けることができます。自分のインジケータと計算結果を追加することで、このアイデアを改善することができます。

コードを理解してプログラム自体をコンパイルしたくない人の為に、マーケットですでに既製のアプリケーションTrading Exposureがあります。

この記事には、テスト用のファイルと、この記事で紹介したコードの詳細な調査があります。

ファイル名 コメント
MQL5\Experts\TradePanel\TradePanel.mq5 グラフィカルインターフェイスによる手動取引のエキスパートアドバイザー
MQL5\Experts\TradePanel\Program.mqh プログラムクラスファイル
MQL5\Experts\TradePanel\CreateGUI.mqh Program.mqhファイルのプログラムクラスからグラフィカルインターフェイスを作成するための実装方法を含むファイル
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh 更新したCTableクラス
MQL5\Include\EasyAndFastGUI\Keys.mqh 更新したCKeysクラス