English Русский 中文 Español Deutsch Português
手動取引のサポーターを作成する

手動取引のサポーターを作成する

MetaTrader 5 | 15 6月 2016, 10:15
2 984 0
Dmitriy Gizlyk
Dmitriy Gizlyk

概論

この記事では、FXの手動取引を支持する人に役立つ、トレードパネルのゼロからの作成例をご紹介します。

1. トレードパネルに必要な機能を定義しましょう

まず初めに自分の為に、どのような結果を得たいかを明確にする必要があります。私達が作成するパネルにどのような機能を求め、どのようなデザインにするのが使い勝手が良いか決める必要があるのです。この記事では、私自身のトレードパネルへのビジョンを提案していますが、勿論皆さんのご意見も喜んで取り入れます。私の新しい記事でそれらを検証していければと思っています。 

さて、私達のパネルには、必ず以下の要素を取り入れる必要があります。

  1. 買いと売りボタン
  2. 通貨ペアや口座ごとまたは片建て(買い注文または売り注文)の全ポジションの決済ボタン
  3. ストップロスやテイクプロフィットのレベルをポイントや証拠金通貨で指定をする機能(1つのパラメータの入力時に、もう一つが自動的に調整される必要があります)。
  4. パネルは、手動で設定したパラメータに基づいて(項目2)、自動的にストップロスとテイクプロフィットのレベルを算出し、それをチャート上に表示します。
  5. トレーダーには、チャート上でストップロスまたはテイクプロフィットレベルを動かす機能が必要です。またその時、全ての変更は変更した数値と共にパネルに表示される必要があります。
  6. パネルは設定したリスクパラメータ(証拠金通貨または現在の残高の割合)に従い、予想される取引ボリュームを算出する必要があります。
  7. トレーダーには、取引ボリュームを独自に指定する機能が必要です。この時、それに応じて対応するパラメータが自動的に再計算される必要があります。
  8. パネルは、どのパラメータがトレーダーによって設定され、どれが自動的に計算されたものかを記憶する必要があります。これは、トレーダーが設定したパラメータが、今後の再計算時にできる限り変わらないようにする為に必要なものです。
  9. パネルは、再起動時に再度設定をする必要がないよう、設定されたパラメータを全て保存する必要があります。

2. パネルのグラフィックレイアウトを作成しましょう

白紙の紙を手に取り、取り入れる必要のある要素を全て配置しつつ、私達の未来のトレードパネルを描いていきましょう。

未来のトレードパネルのデザインを設計する時には、その実用性について考える必要があります。まず第一に、パネルは十分に情報量が多いもので、かつ読みやすく、余分な要素で負担が過重にならないものであるべきです。私達はこれは、画面上のただの美しい絵ではなく、トレーダーが実際に使用するツールであるということを常に覚えておく必要があります。

これが私の案です。

デザイン

3. MQL5言語でのパネルレイアウトの作成

3.1.  下地

最終目標が定まったら、MQL5のコードでそれを具現化しましょう。この為には、私達が作業を行うことができる標準ライブラリをできる限り最大限に使用していきます。MQL5には、ダイアログボックス作成の基礎となるCAppDialogクラスがあります。このクラスのベースに、私達のパネルを作成します。
その為に、クラスの複製を作成し、OnInit()関数で初期化します。

#include <Controls\Dialog.mqh>

class CTradePanel : public CAppDialog
  {
public:
                     CTradePanel(void){};
                    ~CTradePanel(void){};
  };

CTradePanel TradePanel;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   // Creat Trade Panel
   if(!TradePanel.Create(ChartID(),"Trade Panel",0,20,20,320,420))
     {
      return (INIT_FAILED);
     }
   // Run Trade Panel
   TradePanel.Run();
//---
   return(INIT_SUCCEEDED);
  }

この簡単な操作の結果こそが、私達の未来のパネルの下地です。

下地

3.2. 必要なオブジェクトの宣言

ここで、私達の下地に必要な制御要素を入れていきます。この為に各制御要素に対応するクラスのオブジェクトを作成します。オブジェクトの作成は、CLabel、CEdit、CButton、CBmpButtonの標準クラスを使用して行います。

必要なファイルを追加し、CTradePanelクラスの為のCreat()関数を作成します。

#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

私は意図的に"Edit.mqh"と"BmpButton.mqh"のファイルを入れませんでした。これらのファイルは、"Dialog.mqh"からすでに呼び出されているからです。

次のステップは、パネル上の各オブジェクトにCTradePanelクラスで適切な型の変数を宣言し、全ての要素が配置されるCreat(..)を宣言します。注意:変数の宣言とCTradePanelクラス内のその他の動作は、"private"ブロックで宣言します。Creat(...)のようなクラスの外部から呼び出すことができる関数は、"public"ブロックで宣言されます。

class CTradePanel : public CAppDialog
  {
private:

   CLabel            ASK, BID;                        // Display Ask and Bid prices
   CLabel            Balance_label;                   // Display label "Account Balance"
   CLabel            Balance_value;                   // Display Account balance
   CLabel            Equity_label;                    // Display label "Account Equity"
   CLabel            Equity_value;                    // Display Account Equity
   CLabel            PIPs;                            // Display label "Pips"
   CLabel            Currency;                        // Display Account currency
   CLabel            ShowLevels;                      // Display label "Show"
   CLabel            StopLoss;                        // Display label "Stop Loss"
   CLabel            TakeProfit;                      // Display label "TakeProfit"
   CLabel            Risk;                            // Display label "Risk"
   CLabel            Equity;                          // Display label "% to Equity"
   CLabel            Currency2;                       // Display Account currency
   CLabel            Orders;                          // Display label "Opened Orders"
   CLabel            Buy_Lots_label;                  // Display label "Buy Lots"
   CLabel            Buy_Lots_value;                  // Display Buy Lots value 
   CLabel            Sell_Lots_label;                 // Display label "Sell Lots"
   CLabel            Sell_Lots_value;                 // Display Sell Lots value 
   CLabel            Buy_profit_label;                // Display label "Buy Profit"
   CLabel            Buy_profit_value;                // Display Buy Profit value 
   CLabel            Sell_profit_label;               // Display label "Sell Profit"
   CLabel            Sell_profit_value;               // Display Sell profit value 
   CEdit             Lots;                            // Display volume of next order
   CEdit             StopLoss_pips;                   // Display Stop loss in pips
   CEdit             StopLoss_money;                  // Display Stop loss in accaunt currency
   CEdit             TakeProfit_pips;                 // Display Take profit in pips
   CEdit             TakeProfit_money;                // Display Take profit in account currency
   CEdit             Risk_percent;                    // Display Risk percent to equity
   CEdit             Risk_money;                      // Display Risk in account currency
   CBmpButton        StopLoss_line;                   // Check to display StopLoss Line
   CBmpButton        TakeProfit_line;                 // Check to display TakeProfit Line
   CBmpButton        StopLoss_pips_b;                 // Select Stop loss in pips
   CBmpButton        StopLoss_money_b;                // Select Stop loss in accaunt currency
   CBmpButton        TakeProfit_pips_b;               // Select Take profit in pips
   CBmpButton        TakeProfit_money_b;              // Select Take profit in account currency
   CBmpButton        Risk_percent_b;                  // Select Risk percent to equity
   CBmpButton        Risk_money_b;                    // Select Risk in account currency
   CBmpButton        Increase,Decrease;               // Increase and Decrease buttons
   CButton           SELL,BUY;                        // Sell and Buy Buttons
   CButton           CloseSell,CloseBuy,CloseAll;     // Close buttons
   
public:
                     CTradePanel(void){};
                    ~CTradePanel(void){};
  //--- Create function
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   
  };

3.3. オブジェクトグループの為の初期化プロセスの作成

Creat(...)関数の本体を書く時がきました。この関数では、上記の宣言されたオブジェクトを全て初期化する必要があるという点にご注意ください。私達は4種類の45のオブジェクトを宣言したことは、簡単に計算できます。つまり、各タイプごとにオブジェクトの初期化プロセスを4つ書くのが合理的ということです。クラスの初期化関数は"private"ブロックで宣言します。

勿論、配列でもオブジェクトの宣言はできますが、この場合オブジェクトの変数名とその機能の間の繋がりを失うことになり、これからのオブジェクトを使用した作業を複雑化してしまいます。その為、コードの分かりやすさと、使いやすさを優先しました(今後克服しなければならない障害物を自分で自分に作成するようなことはしません)。

CLabelクラス

私達のパネル上のテキスト情報を表示する為にCLabelクラスを使用します。初期化関数の作成時に、どの関数がこのクラスの全ての要素に共通し、どの関数がどんな点において異なるのかを定義する必要があります。この場合には、以下のような違いがあります。

  • オブジェクト名
  • 表示されるテキスト
  • 要素の座標
  • アンカーポイントを基準にしたオブジェクトの平均化

違いを明確にしたところで、普遍的なものにする為に、関数のパラメータを引き渡す必要があるものを決定し、プロセスの中で生成することができるものを決定しましょう。

オブジェクトを使用する時には、チャート上の全てのオブジェクトは個々の名前を持つ必要があるということを覚えておいてください。いつものように、各オブジェクト名を自分で設定するか、プログラムによって生成するか、選択はプログラマー次第です。ユニバーサル関数を作成しながら、私はオブジェクト名をプログラム内で生成する方法を選びました。その為に、シリアル番号を追加したクラスの為のオブジェクト名を定義しました。

string name=m_name+"Label"+(string)ObjectsTotal(chart,-1,OBJ_LABEL);

表示されるテキスト、オブジェクトの座標、そしてアンカーポイントを基準にしたオブジェクトの平均化を、パラメータを使用して関数に引き渡します。プログラムコードを読みやすくし、プログラマーの作業をしやすくする為に、オブジェクトの平均化の為のリストを作成します。

  enum label_align
     {
      left=-1,
      right=1,
      center=0
     };

また、プロセスパラメータの中でチャートコード、サブウィンドウ番号、作成するオブジェクトへの参照を指定する必要があります。

関数自体に、このようなクラスの各オブジェクトを使用して実行するプロセスを書き込みます。

  • 親クラスのCreate(...)関数を使用し、オブジェクトを作成し、
  • それからオブジェクトに必要なテキストを配置します。
  • アンカーポイントに対しオブジェクトを平均化します。
  • ダイアログボックスの『コンテナ』にオブジェクトを追加します。
bool CTradePanel::CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align)
  {
   // All objects must to have separate name
   string name=m_name+"Label"+(string)ObjectsTotal(chart,-1,OBJ_LABEL);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,0,0))
     {
      return false;
     }
   //--- Addjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- Aling text to Dialog box's grid
   ObjectSetInteger(chart,object.Name(),OBJPROP_ANCHOR,(align==left ? ANCHOR_LEFT_UPPER : (align==right ? ANCHOR_RIGHT_UPPER : ANCHOR_UPPER)));
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

CButtonクラス

CButtonクラスは、ラベルをもつ長方形のボタンを作成する為にあります。これは、発注と決済の標準ボタンになります。

このオブジェクトのクラスで作業を開始するにあたって、前述のようなアプローチを適用したいと思います。また、動作の特性を考慮する必要があります。まず初めに、親クラスではテキストはすでに中央に揃えられているため、ボタンのテキストも揃える必要があります。ここにパラメータで引き渡されるボタンのサイズが表示されます。

また、ボタンに現在の状態(ボタンが押されているかいないか)が表示され、その他には、ボタンが押されている状態で、ロックできるかどうかが表示されます。したがって、これらの追加オプションは、オブジェクト初期化プロセスに書き込む必要があります。私達のボタンに、ロックを無効にし、『押していない』状態を設定します。

bool CTradePanel::CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size)
  {
   // All objects must to have separate name
   string name=m_name+"Button"+(string)ObjectsTotal(chart,-1,OBJ_BUTTON);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
      return false;
     }
   //--- Addjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- set button flag to unlock
   object.Locking(false);
   //--- set button flag to unpressed
   if(!object.Pressed(false))
     {
      return false;
     }
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

CEditクラス

CEditクラスは、データ入力オブジェクトの作成の為にあります。私達のパネルのこのようなオブジェクトは、取引ボリュームやストップロス/テイクプロフィットレベル(ポイントや証拠金通貨)、リスクレベルの入力セルに関係しています。

前述した2つのクラスのものと同じ方法を使用します。しかし、ボタンの時とは違いこのクラスの初期化プロセスでは、セル内でどのようにテキストを揃えるかを指定する必要があります。また、セルに入力または送信されるあらゆる情報は、常にテキストとして扱われるということを覚えておきましょう。したがって、数字を表示する為のオブジェクトを引き渡し、初めにそれらをテキストに変換する必要があります。

CEditクラスのオブジェクトは、ボタンとは違い、『押している』/『押していない』という状態はありませんが、このクラスはプログラム動作時にユーザーが編集することができないオブジェクトの作成に使用されます。私達の場合では、全てのオブジェクトはユーザーが編集できるようにする必要があります。これを私達の初期化関数に指定します。 

bool CTradePanel::CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size)
  {
   // All objects must to have separate name
   string name=m_name+"Edit"+(string)ObjectsTotal(chart,-1,OBJ_EDIT);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
      return false;
     }
   //--- Addjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- Align text in Edit box
   if(!object.TextAlign(ALIGN_CENTER))
     {
      return false;
     }
   //--- set Read only flag to false
   if(!object.ReadOnly(false))
     {
      return false;
     }
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

CBmpButtonクラス

CBmpButtonクラスは、ラベルの代わりにグラフィックオブジェクトを使う非標準ボタンの作成の為にあります。全てのユーザーに分かりやすいこれらのボタンは、様々なアプリケーションの為に標準化された制御要素の作成時に使用します。私達のケースでは、このクラスを使用し作成していきます。

  • (通貨形式またはポイント、またはリスクパーセンテージでの)ストップロスやテイクプロフィット、リスクが表示されるオプションボタン
  • チャート上のストップロスやテイクプロフィットレベルの表示/非表示を固定するチェックボックス
  • 取引ボリュームの増減ボタン

このオブジェクトのクラスをを使用した作業は、CButtonクラスを使用した作業と似ています。違いは、テキストの代わりの押しているまたは押していない状態のボタンの為のチャートオブジェクトの引き渡しにあります。私達のパネルにはMQL5に付属するボタンの画像を使用します。また、一つのファイルで完成したソフトウェア製品を配信することができるように、リソースとしてこれらの画像を記述します。

#resource "\\Include\\Controls\\res\\RadioButtonOn.bmp"
#resource "\\Include\\Controls\\res\\RadioButtonOff.bmp"
#resource "\\Include\\Controls\\res\\CheckBoxOn.bmp"
#resource "\\Include\\Controls\\res\\CheckBoxOff.bmp"
#resource "\\Include\\Controls\\res\\SpinInc.bmp"
#resource "\\Include\\Controls\\res\\SpinDec.bmp"

また、このクラスの全ての要素は、ロットの増減ボタン以外はロックされる(つまり、『押している』または『押していない』状態が保存される)事を考慮する必要があります。したがって、初期化関数に追加パラメータを追加します。

//+------------------------------------------------------------------+
//| Create BMP Button                                                |
//+------------------------------------------------------------------+
bool CTradePanel::CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock)
  {
   // All objects must to have separate name
   string name=m_name+"BmpButton"+(string)ObjectsTotal(chart,-1,OBJ_BITMAP_LABEL);
   //--- Calculate coordinates
   uint y1=(uint)(y-(Y_STEP-CONTROLS_BUTTON_SIZE)/2);
   uint y2=y1+CONTROLS_BUTTON_SIZE;
   //--- Call Create function
   if(!object.Create(m_chart_id,name,m_subwin,x-CONTROLS_BUTTON_SIZE,y1,x,y2))
      return(false);
   //--- Assign BMP pictuers to button status
   if(!object.BmpNames(BmpOFF,BmpON))
      return(false);
   //--- Add object to controls
   if(!Add(object))
      return(false);
   //--- set Lock flag to true
   object.Locking(lock);
//--- succeeded
   return(true);
  }

オブジェクト作成の関数を記述し、私達のクラスの"private"ブロックでこれらの関数を然るべき方法で宣言します。

private:

   //--- Create Label object
   bool              CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align);
   //--- Create Button
   bool              CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size);
   //--- Cleate Edit object
   bool              CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size);
   //--- Create BMP Button
   bool              CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock);

3.4. 全てのエレメントをそれぞれの場所に配置します

オブジェクトの各クラスの初期化関数を書いた今、これを私達の取引パネルの為に書く時がきました。この関数の主要な課題は、パネルの各オブジェクトの座標を計算することと、対応する初期化関数を呼び出すことによって全てのオブジェクトを一歩ずつ作成することです。

もう一度思い出していただきたいのですが、パネル上の要素はユーザーの使い勝手が良いように配置する必要があり、わかりやすいデザインである必要があります。パネルのレイアウトの作成時に、私達はこの問題に注目しましたが、ここでもこの構想を堅持していきます。同時に、最終的なプログラムで私達のクラスを使用する時に、パネルのサイズが異なることがあるということを理解する必要があります。取引パネルのサイズ変更の際に私達のデザインが保存されるようにする為に、私達は明示的に指定するのではなく、各オブジェクトの座標を計算する必要があります。この目的から特別なビーコンを作成します。

  • ウィンドウの端から最初の制御要素までの距離
  • 高さ制御の要素間の距離
  • 制御要素の高さ
   #define  Y_STEP   (int)(ClientAreaHeight()/18/4)      // height step betwine elements
   #define  Y_WIDTH  (int)(ClientAreaHeight()/18)        // height of element
   #define  BORDER   (int)(ClientAreaHeight()/24)        // distance betwine boder and elements

このようにして、最初の制御要素の座標と前のものに対する各後続の座標を計算することができます。
また、私達のパネルの最適なサイズを決定することで、これらを引き渡される関数のパラメータの為のデフォルトの数値として指定することができます。

bool CTradePanel::Create(const long chart,const string name,const int subwin=0,const int x1=20,const int y1=20,const int x2=320,const int y2=420)
  {
      // At first call creat function of parents class
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
     {
      return false;
     }
   // Calculate coofrdinates and size of BID object
   // Coordinates calculate in dialog box, not in chart
   int l_x_left=BORDER;
   int l_y=BORDER;
   int y_width=Y_WIDTH;
   int y_sptep=Y_STEP;
   // Creat object
   if(!CreateLabel(chart,subwin,BID,DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits),l_x_left,l_y,left))
     {
      return false;
     }
   // Adjust font size for object
   if(!BID.FontSize(Y_WIDTH))
     {
      return false;
     }
   // Repeat same functions for other objects
   int l_x_right=ClientAreaWidth()-20;
   if(!CreateLabel(chart,subwin,ASK,DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits),l_x_right,l_y,right))
     {
      return false;
     }
   if(!ASK.FontSize(Y_WIDTH))
     {
      return false;
     }
   l_y+=2*Y_WIDTH;
...................
  }

関数のフルコードは、添付例で参照することができます。

私達の作業の結果、以下のパネルが作成されました。

パネル

しかし、これはレイアウトであり、グラフィック上の美しい絵でしかありません。次のステップでは、私達は作業を終了します。

4. 『画像の活性化』

私達の取引パネルのグラフィックレイアウトを作成した今、これに起こる事象に対して反応する方法をレクチャーする時間が来ました。イベントハンドラの作成と設定の為に、どのイベントにどのように反応する必要があるかを定義する必要があります。

4.1. ツールの価格変更

ツールの価格変更時に、エキスパートアドバイザのOnTick()関数を始動させるNewTickイベントをМТ5のターミナルで生成します。したがって、この関数からイベントを処理する、私達のクラスの対応する関数を呼び出す必要があります。その関数に類似名OnTick()を追加し、"public"ブロックで宣言します(つまりこの関数は外部プログラムから呼び出されます)。

public:

   virtual void      OnTick(void);
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   TradePanel.OnTick();
  }

ツールの価格が変更されると、パネル上ではどんな変化が起こるのか?私達が初めにするべきことは、私達のパネル上のAskとBid値を変更することです。
//+------------------------------------------------------------------+
//| Event "New Tick                                                  |
//+------------------------------------------------------------------+
void CTradePanel::OnTick(void)
  {
   //--- Change Ask and Bid prices on panel
   ASK.Text(DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));
   BID.Text(DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));

それから、口座に未決ポジションがある場合、パネル上の資金額の値(Equity)を変更します。念のため、未決ポジションがない場合にも、口座上の実際の資金額とパネル上の表示が一致しているかどうかをチェックする機能を追加しました。これによって『緊急事態』の後にも実際の資金額を表示することができます。したがって、未決ポジションの有無をチェックする必要はありません。すぐにパネル上の表示と口座の現在の資金額の一致をチェックをしましょう。必要に応じて、パネルに本当の値を出力します。

//--- Сheck and change (if neccesory) equity
   if(Equity_value.Text()!=DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
     {
      Equity_value.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
     }
   

残高の表示の為にも同様の反復を行います。
『取引操作実行時にだけ変わるのに、なんで各ティックで残高をチェックするの?』という質問が予想されます。確かにその通りですが、取引イベントへの反応についてはもう少し後でお話します。しかし、私達のパネルが起動していない、またはターミナルとサーバーの接続が切れている場合にも、取引操作を実行するわずかな可能性があります。パネル上に常に(様々な外部の状況下でも)現在の残高が表示されるようにする為に、私はこの操作を追加しました。

次のステップで価格の変更時に私達は現在の商品に未決ポジションがあるかどうかをチェックし、もしそれがある場合には、BuyまたはSell欄のポジションの現在の利益とボリュームの値をチェックし調整します。

//--- Check and change (if neccesory) Buy and Sell lots and profit value.
   if(PositionSelect(_Symbol))
     {
      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           Buy_profit_value.Text(DoubleToString(PositionGetDouble(POSITION_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
           if(Buy_Lots_value.Text()!=DoubleToString(PositionGetDouble(POSITION_VOLUME),2))
              {
               Buy_Lots_value.Text(DoubleToString(PositionGetDouble(POSITION_VOLUME),2));
              }
           if(Sell_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
              {
               Sell_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
              }
           if(Sell_Lots_value.Text()!=DoubleToString(0,2))
              {
               Sell_Lots_value.Text(DoubleToString(0,2));
              }
           break;
         case POSITION_TYPE_SELL:
           Sell_profit_value.Text(DoubleToString(PositionGetDouble(POSITION_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
           if(Sell_Lots_value.Text()!=DoubleToString(PositionGetDouble(POSITION_VOLUME),2))
              {
               Sell_Lots_value.Text(DoubleToString(PositionGetDouble(POSITION_VOLUME),2));
              }
           if(Buy_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
              {
               Buy_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
              }
           if(Buy_Lots_value.Text()!=DoubleToString(0,2))
              {
               Buy_Lots_value.Text(DoubleToString(0,2));
              }
           break;
        }
     }
   else
     {
      if(Buy_Lots_value.Text()!=DoubleToString(0,2))
        {
         Buy_Lots_value.Text(DoubleToString(0,2));
        }
      if(Sell_Lots_value.Text()!=DoubleToString(0,2))
        {
         Sell_Lots_value.Text(DoubleToString(0,2));
        }
      if(Buy_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
        {
         Buy_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
        }
      if(Sell_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
        {
         Sell_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
        }
     }

また、チャート上のストップロスやテイクプロフィットのレベルを表示するチェックボックスの状態をチェックすることを忘れないようにしましょう。必要に応じて、ラインの位置を修正しましょう。これらの関数を呼び出すコードを追加します。詳細は以下をご覧ください。

   //--- Move SL and TP lines if necessary
   if(StopLoss_line.Pressed())
     {
      UpdateSLLines();
     }
   if(TakeProfit_line.Pressed())
     {
      UpdateTPLines();
     }
   return;
  }

4.2. 編集欄への数値の入力

私達のパネルには編集欄があり、勿論、私達は入力する情報の取得と処置を設定する必要があります。

編集欄への情報の入力は、ChartEventのイベントグループに関わるグラフィックオブジェクトの変更です。このグループのイベントは、OnChartEvent関数で処理されます。この関数には、4つの入力パラメータ(イベント識別子、long/double/stringのタイプに関わるイベントに特徴づけられる3つのパラメータ)があります。前述の場合のように、私達のクラスでイベントハンドラを作成し、イベントを特徴づける全ての入力パラメータの引き渡しを伴うOnChartEvent関数からそれを呼び出します。話を少し先に進めますが、この関数によって取引パネルのボタンを押すという事象も処理されることになります。そのためこの関数は、イベントを分析し、特定イベントの処理の関数を呼び出すディスパッチャ―となります。そして、イベントについての情報を親クラスに書かれているプロセス実行の為の親クラスの関数に引き渡します。

public:

   virtual bool      OnEvent(const int id,const long &lparam, const double &dparam, const string &sparam);

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   TradePanel.OnEvent(id, lparam, dparam, sparam);
  }

このディスパッチャ―を構築する為に、マクロ置換を使用します。

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
EVENT_MAP_END(CAppDialog)

したがって、全てのイベント処理関数を私達のクラスの"private"ブロックで宣言する必要があります。

private:

   //--- On Event functions
   void              LotsEndEdit(void);                              // Edit Lot size
   void              SLPipsEndEdit(void);                            // Edit Stop Loss in pips
   void              TPPipsEndEdit(void);                            // Edit Take Profit in pips
   void              SLMoneyEndEdit(void);                           // Edit Stop Loss in money
   void              TPMoneyEndEdit(void);                           // Edit Take Profit in money
   void              RiskPercentEndEdit(void);                       // Edit Risk in percent
   void              RiskMoneyEndEdit(void);                         // Edit Risk in money

 編集欄から取得したデータを保存する為に、"private"ブロックで追加パラメータを入力します

private:

   //--- variables of current values
   double            cur_lot;                         // Lot of next order
   int               cur_sl_pips;                     // Stop Loss in pips
   double            cur_sl_money;                    // Stop Loss in money
   int               cur_tp_pips;                     // Take Profit in pips
   double            cur_tp_money;                    // Take Profit in money
   double            cur_risk_percent;                // Risk in percent
   double            cur_risk_money;                  // Risk in money

特定のイベント例ー準備された取引のボリュームの入力をします。これらのフィールドへのあらゆる情報の入力は、その内容に関係なくテキスト入力として受け取られます。本質的には、欄へのテキスト入力時に、一連のイベントが生成されます(オブジェクトへマウスを合わせる、マウスのキーを押す、編集欄の始め、キーボードのキーを押す、編集欄の終わりなど)。私達に興味があるのは、情報の入力を終了する時の最後のイベントです。そのため、関数の呼び出しは"ON_END_EDIT"イベントによって行います。

始めに私達がこのイベント処理関数で行うべきことは、入力したテキストを読み、それをdouble型の値に変換することです。

それから、入手した数値の『正常化』を行う、つまり、取引商品の条件に合わせてこれを実行する必要があります(1つの注文の最小/最大ボリューム、またボリュームの段階的変更)。この操作を実行する為に、別個の関数を書きます。なぜなら、これは取引ボリュームの増減ボタンを押す際にも必要になるからです。取得した数値は、未来の取引の実際のボリュームをトレーダーに通知する為に、パネルに戻す必要があります。

//+------------------------------------------------------------------+
//| Read lots value after edit                                       |
//+------------------------------------------------------------------+
void CTradePanel::LotsEndEdit(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));

また、オプションボタンの現在の設定に応じて、全ての残りの編集欄の数値をパネル上で再計算し変更する必要があります。これは、取引量を変えると、ストップロス(ポイントでストップロスを指定した場合)またはストップロスレベル(通貨量でストップロスを指定した場合)で決済した場合の、リスク額が変わるからです。ストップロスはリスクレベルに引っ張られます。同様の状況は、テイクプロフィットの数値でも起こります。勿論、これらの操作は対応する関数を介して構成されます。

   //--- Check and modify value of other labels 
   if(StopLoss_money_b.Pressed())
     {
      StopLossPipsByMoney();
     }
   if(TakeProfit_money_b.Pressed())
     {
      TakeProfitPipsByMoney();
     }
   if(StopLoss_pips_b.Pressed())
     {
      StopLossMoneyByPips();
     }
   if(TakeProfit_pips_b.Pressed())
     {
      TakeProfitMoneyByPips();
     }

毎日ユーザーが使うツールを構築する時には、常に『使いやすさ』に注意する必要があります。ここでは、冒頭に書いた私達の取引パネルの8つの機能について思い出しましょう。『パネルはどのパラメータがトレーダーによって設定され、どれが自動的に計算されたものかを記憶する必要があります。これは、トレーダーが設定したパラメータが、今後の再計算時にできる限り変わらないようにする為にひつようなものです。』言い換えれば、将来的に、ポイントでストップロスを変更する時に、トレーダーが取引ボリュームまたはリスクレベルを最後のものに変更したことを思い出す必要があります。必要に応じて、最後に入力されたデータを変更せずに残す必要があります。
この目的から、"private"ブロックにRiskByValue変数を入力し、このイベントの処理関数にtrue値を割り当てます。

private:
   bool              RiskByValue;                     // Flag: Risk by Value or Value by Risk
   RiskByValue=true;
   return;
  }

編集可能欄の調整関数の構築原理は、最も包括的な機能を持っているという理由から、StopLossMoneyByPips関数の例で見ていくことにします。

1. 本質的に、この関数は3つの場合に呼び出されます。ロットが変更された場合、ポイントでのストップロス欄の数値の変更時、そしてストップロスラインの移動時です。したがって、私達が始めにすべきことは、取引ボリュームの現在の値をチェックすることです。それがツールの分類と一致しない場合、パネル上に表示される数値を市場の実際の数値に直す必要があります。

//+------------------------------------------------------------------+
//|  Modify SL money by Order lot and SL pips                        |
//+------------------------------------------------------------------+
void CTradePanel::StopLossMoneyByPips(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));

2. 予想されるリスクの金銭的数値の計算の為の二つ目の構成物は、1ロットの未決ポジションの時に1ティック分ツール価格を変更する時の資金額の変更です。この為に、1ティックの価格と商品価格の最小変更サイズを取得します。

   double tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
   double tick_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);

3. 取得したデータに基づき、予想される損失を計算し、取得した数値をパネルの対応する欄に入力します。

   cur_sl_money=NormalizeDouble(tick_value*cur_lot*(tick_size/_Point)*cur_sl_pips,2);
   StopLoss_money.Text(DoubleToString(cur_sl_money,2));

4. ストップロスでの決済時に予想される損失は、お金の面で私達のリスクとなるという点に注意する必要があります。したがって、私達はお金の面でのリスク欄に計算した値のバックアップを行い、それから相対リスク値(リスクパーセンテージ)を計算します。

   cur_risk_money=cur_sl_money;    Risk_money.Text(DoubleToString(cur_risk_money,2));    cur_risk_percent=NormalizeDouble(cur_risk_money/AccountInfoDouble(ACCOUNT_BALANCE)*100,2);    Risk_percent.Text(DoubleToString(cur_risk_percent,2));
return;
}

ポイントでのストップロスの計算関数は、リスクが変わらないことを除いて上記の関数とは逆になるが、チャート上のストップロスレベルの表示ラインの位置を調整する必要があります。

テイクプロフィットの値の調整も同様に行われます。

このように、他の欄の編集のイベント処理の為に関数を構成します。この時、欄の編集時にオプションボタンの状態を変更する必要があることを覚えておきましょう。また、各関数でボタンの状態のバックアップをしなくてもいいように、ボタンを押す処理をする関数を呼び出しましょう。 

4.3. オプションボタンを押すイベントの処理

オプションボタンは、定義されたセット(グループ)から、一つのオプション(項目)を選択することができるインターフェイスの要素です。
したがって、オプションボタンの一つを押す時は、関連するボタンの状態を変更する必要があります。同時に、オプションボタンのスイッチ自体は、パラメータの再計算をもたらすことはありません。
このように、オプションボタンを押すというイベント処理の関数は、関連するオプションボタンの状態のみ変更します。つまり、押しているオプションボタンを『押している』状態にし、他の従属するボタンを『押していない』状態にします。

技術的な面では、ボタンを押すことはChartEventのイベントグループに関連しています。そのため、処理は編集欄と同じように行われます。イベント処理関数を"private"ブロックで宣言します。

private:

   //--- On Event functions
   void              SLPipsClick();                                  // Click Stop Loss in pips
   void              TPPipsClick();                                  // Click Take Profit in pips
   void              SLMoneyClick();                                 // Click Stop Loss in money
   void              TPMoneyClick();                                 // Click Take Profit in money
   void              RiskPercentClick();                             // Click Risk in percent
   void              RiskMoneyClick();                               // Click Risk in money

イベントハンドラのマクロ置換を補います。

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
EVENT_MAP_END(CAppDialog)

イベント処理の関数自体は以下のようになります。

//+------------------------------------------------------------------+
//| Click Stop Loss in pips                                          |
//+------------------------------------------------------------------+
void CTradePanel::SLPipsClick(void)
  {
   StopLoss_pips_b.Pressed(cur_sl_pips>0);
   StopLoss_money_b.Pressed(false);
   Risk_money_b.Pressed(false);
   Risk_percent_b.Pressed(false);
   return;
  }

 イベント処理の全ての関数は、添付のコードで参照することができます。

4.4. 取引ボリューム変更ボタンを押す

オプションボタンとの違いは、取引ボリューム変更ボタンを押した時に、プログラムが私達がコードに記述するべき一連の操作を実行する必要があるという点です。まず、取引ボリュームの変更値へのcur_lot変数の値の増減です。それから、ツールの予想される最小/最大値を用いて取得した数値を比較する必要があります。追加オプションとして、このようなボリュームの未決注文の為の自由な資金の有無をチェックすることをお勧めします。なぜなら、トレーダーが発注する時に口座上に資金が不足しているということがあり得るからです。その後、パネルに新しい取引ボリュームの値を入力し、編集可能欄に取引ボリュームの数値を手動で入力する場合は関連する数値を編集する必要があります。

また今までのように、私達の関数をprivateブロックで宣言します。

private:
................
   //--- On Event functions
................
   void              IncreaseLotClick();                             // Click Increase Lot
   void              DecreaseLotClick();                             // Click Decrease Lot

マクロ置換で割り込み処理関数を補います。

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
   ON_EVENT(ON_CLICK,Increase,IncreaseLotClick)
   ON_EVENT(ON_CLICK,Decrease,DecreaseLotClick)
EVENT_MAP_END(CAppDialog)

イベント処理関数を見てみましょう。

//+------------------------------------------------------------------+
//|  Increase Lot Click                                              |
//+------------------------------------------------------------------+
void CTradePanel::IncreaseLotClick(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text())+SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));
   //--- Call end edit lot function
   LotsEndEdit();
   return;
  }

まず、現在の取引ボリュームの値を考慮し、それをツールの仕様から一歩増やします。それからすぐに、以前にすでに見たことがあるNormalizeLots関数によるツールの分類によって取得した数値を入力します。

そして、この関数では以前にすでに全ての必要なプロセスを書き込んでいるので、入力ウィンドウでロットボリュームの変更処理関数を呼び出します。

ロットを減らす関数も同様に構築します。

4.5. チェックボックスの状態変更

次のステップでは、チェックボックスを押すことに反応するイベントハンドラを構築します。私達のパネルには、チャート上のストップロス/テイクプロフィットレベルの表示のオン/オフの為の2つのチェックボックスがあります。

チェックボックスの状態を変更すると何が起こるでしょうか?基本的に、このイベントの主要な機能はチャート上のライン表示です。この問題は2つの方法で解決することができます。

  1. 押すたびにラインを作成/削除する
  2. パネルの全てのオブジェクトと一緒に一度ラインを作成し、チェックボックスの状態を変更した時にそれらを表示/非表示にする

私は2つ目の方法が好きです。この目的から、もう一つのライブラリを入れましょう。

#include <ChartObjects\ChartObjectsLines.mqh>

それから、privateボックスで水平線のオブジェクトを宣言し、それらの初期化関数を宣言します。

private:
.................
   CChartObjectHLine BuySL, SellSL, BuyTP, SellTP;    // Stop Loss and Take Profit Lines
   
   //--- Create Horizontal line
   bool              CreateHLine(long chart, int subwindow,CChartObjectHLine &object,color clr, string comment);

水平線の初期化プロセスを書きます。始めにチャート上にラインを作成します。

//+------------------------------------------------------------------+
//| Create horizontal line                                           |
//+------------------------------------------------------------------+
bool CTradePanel::CreateHLine(long chart, int subwindow,CChartObjectHLine &object,color clr, string comment)
  {
   // All objects must to have separate name
   string name="HLine"+(string)ObjectsTotal(chart,-1,OBJ_HLINE);
   //--- Create horizontal line
   if(!object.Create(chart,name,subwindow,0))
      return false;

それから色やラインタイプを指定し、オブジェクトにカーソルを合わせたときに表示されるコメントを追加します。

   //--- Set color of line
   if(!object.Color(clr))
      return false;
   //--- Set dash style to line
   if(!object.Style(STYLE_DASH))
      return false;
   //--- Add comment to line
   if(!object.Tooltip(comment))
      return false;

チャートからラインを隠し、ラインの背景表示にします。

   //--- Hide line 
   if(!object.Timeframes(OBJ_NO_PERIODS))
      return false;
   //--- Move line to background
   if(!object.Background(true))
      return false;

私達のパネルのオプションの一つが、チャート上のストップロス/テイクプロフィットレベルを移動する機能を与えるので、ユーザーにラインを強調表示する機能を提供します。

   if(!object.Selectable(true))
      return false;
   return true;
  }

ここで私達の取引パネルを作成する関数にラインの初期化を追加します。

//+------------------------------------------------------------------+
//| Creat Trade Panel function                                       |
//+------------------------------------------------------------------+
bool CTradePanel::Create(const long chart,const string name,const int subwin=0,const int x1=20,const int y1=20,const int x2=320,const int y2=420)
  {
...................
...................
   //--- Create horizontal lines of SL & TP
   if(!CreateHLine(chart,subwin,BuySL,SL_Line_color,"Buy Stop Loss"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,SellSL,SL_Line_color,"Sell Stop Loss"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,BuyTP,TP_Line_color,"Buy Take Profit"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,SellTP,TP_Line_color,"Sell Take Profit"))
     {
      return false;
     }
    return true;
  }

私達がラインを作成したら、イベント処理関数を直接書きましょう。イベント処理関数は前のイベント処理関数の時と同じように構築します。イベント処理関数をprivateブロックで宣言します。

private:
...............
   void              StopLossLineClick();                            // Click StopLoss Line 
   void              TakeProfitLineClick();                          // Click TakeProfit Line

イベントハンドラに関数の呼び出しを追加します。

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
   ON_EVENT(ON_CLICK,Increase,IncreaseLotClick)
   ON_EVENT(ON_CLICK,Decrease,DecreaseLotClick)
   ON_EVENT(ON_CLICK,StopLoss_line,StopLossLineClick)
   ON_EVENT(ON_CLICK,TakeProfit_line,TakeProfitLineClick)
EVENT_MAP_END(CAppDialog)

最後に、イベント処理関数自体を書きます。関数の始めでチェックボックスの状態をチェックします。次に何が起こるかは、チェックボックスの状態によります。もしチェックが入っている場合、ラインの表示前に、表示レベルを宣言する必要があります。それからチャート上にラインを出力します。

//+------------------------------------------------------------------+
//| Show and Hide Stop Loss Lines                                    |
//+------------------------------------------------------------------+
void CTradePanel::StopLossLineClick()
  {
   if(StopLoss_line.Pressed()) // Button pressed
     {
      if(BuySL.Price(0)<=0)
        {
         UpdateSLLines();
        }
      BuySL.Timeframes(OBJ_ALL_PERIODS);
      SellSL.Timeframes(OBJ_ALL_PERIODS);
     }

もしチェックが入っていない場合、ラインは隠されます。

   else                         // Button unpressed
     {
      BuySL.Timeframes(OBJ_NO_PERIODS);
      SellSL.Timeframes(OBJ_NO_PERIODS);
     }
   ChartRedraw();
   return;
  }

関数の最後でチャートの再描画を呼び出します。

4.6. 取引操作

パネル上の主要な制御要素のイベント処理関数の記述が終わったら、取引操作ボタンを押すイベントの処理に移ります。口座上で取引操作を行う為に、取引操作クラスCTradeが書かれた"Trade.mqh"MQL5標準ライブラリを使用します。

#include <Trade\Trade.mqh>

取引操作のクラスをprivateブロックで宣言します。

private:
................
   CTrade            Trade;                           // Class of trade operations

そして私達のクラスの初期化関数で、取引クラスの初期化を行います。ここでは、取引のマジックナンバー、取引操作実行の為のスリッページレベル、取引注文実行のルールを設定します。

//+------------------------------------------------------------------+
//| Class initialization function                                    |
//+------------------------------------------------------------------+
CTradePanel::CTradePanel(void)
  {
   Trade.SetExpertMagicNumber(0);
   Trade.SetDeviationInPoints(5);
   Trade.SetTypeFilling((ENUM_ORDER_TYPE_FILLING)0);
   return;
  }

必要であれば、マジックナンバーや外部プログラムからのスリッページレベルの設定の為の追加関数をここに追加することができます。このような関数はpublicブロックで宣言する必要があることを忘れないでください。

準備作業が終わったら、ボタンを押す事象の処理関数を書きます。まずいつものようにprivateブロックで関数を宣言します。

private:
.....................
   void              BuyClick();                                     // Click BUY button
   void              SellClick();                                    // Click SELL button
   void              CloseBuyClick();                                // Click CLOSE BUY button
   void              CloseSellClick();                               // Click CLOSE SELL button
   void              CloseClick();                                   // Click CLOSE ALL button

それから、新しい関数で事象処理ディスパッチャ―を補います。

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
...................
   ON_EVENT(ON_CLICK,BUY,BuyClick)
   ON_EVENT(ON_CLICK,SELL,SellClick)
   ON_EVENT(ON_CLICK,CloseBuy,CloseBuyClick)
   ON_EVENT(ON_CLICK,CloseSell,CloseSellClick)
   ON_EVENT(ON_CLICK,CloseAll,CloseClick)
EVENT_MAP_END(CAppDialog)

そして勿論、イベント処理関数を直接書きます。例として購入関数を見てみましょう。『BUY』ボタンを押した時、私達のプログラムはどんなアクションを実行する必要があるのでしょうか?

恐らく、まず初めに取引量を更新する必要があります。ロット欄で数値を読んだら、それをツールの仕様に合わせて入力し、発注に十分な資金があるかを確認し、それからパネルに更新された数値を戻します。

void CTradePanel::BuyClick(void)
  {
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   Lots.Text(DoubleToString(cur_lot,2));

次のステップで、商品の市場価格を取得し、パネル上で指定したパラメータに合わせたストップロス/テイクプロフィットの価格レベルを計算します。

   double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double SL=(cur_sl_pips>0 ? NormalizeDouble(price-cur_sl_pips*_Point,_Digits) : 0);
   double TP=(cur_tp_pips>0 ? NormalizeDouble(price+cur_tp_pips*_Point,_Digits) : 0);

そして最後にブローカーサーバーへ発注リクエストを送信します。またエラーが発生した場合に、エラーについてトレーダーに通知する関数をここに追加する必要があります。

   if(!Trade.Buy(NormalizeLots(cur_lot),_Symbol,price,SL,TP,"Trade Panel"))
      MessageBox("Error of open BUY ORDER "+Trade.ResultComment(),"Trade Panel Error",MB_ICONERROR|MB_OK);;
   return;
  }

その他の取引ボタンを押す処理関数も同じように構築します。これらの関数のコードについての詳細は、添付ファイルで参照することができます。

5. 『手動』でのストップロス/テイクプロフィットレベルの移動

私達は、トレーダーがストップロス/テイクプロフィットレベルを、チャート上の重要なレベルまで頻繁に動かすことを知っています。そして、私の意見では、ユーザーに現在の価格からそのレベルまでのピップ数を計算させるのは間違っていると思っています。その為、ラインを必要なポイントまで動かすだけの機能をユーザーに提供し、その他のことは全てプログラムが行うようにします。

自分自身の為にチャート上のマウスの動きを処理するコードでプログラムをオーバーロードし、オブジェクトを移動するターミナルの標準関数を使用することに決めました。この目的から、ユーザーに水平線を強調したり移動する機能を残しました。"CHARTEVENT_OBJECT_DRAG"イベントはプログラムで処理します。

いつものように、外部プログラムからこの関数を呼び出すので、始めにpublicブロックでイベント処理関数を宣言します。

public:
................
   virtual bool      DragLine(string name);
オブジェクトの名前の転送を伴うイベントの発生時に、主要プログラムのOnChartEvent関数からこの関数の呼び出しを行います。
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      if(TradePanel.DragLine(sparam))
        {
         ChartRedraw();
        }
     }
...........

イベント処理関数では以下のことを行う必要があります。

  • どのラインが動かされたかを判別する(ストップロスまたはテイクプロフィット)
  • ポイントでのインデックス値を計算する
  • 適切なパネルのセルに取得した数値を入力する
  • パネル上の全ての関連するインデックス値を再計算する
  • 必要に応じて、オプションボタンの数値を変える。

最初の3つの項目はイベント処理関数で実行し、残りの項目の実行の為に適切なフィールドの編集を『手動』で行う関数を呼び出します。そして勿論、イベントの処理後にラインから選択を外します。

//+------------------------------------------------------------------+
//| Function of moving horizontal lines                              |
//+------------------------------------------------------------------+
bool CTradePanel::DragLine(string name)
  {
   if(name==BuySL.Name())
     {
      StopLoss_pips.Text(DoubleToString(MathAbs(BuySL.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_ASK))/_Point,0));
      SLPipsEndEdit();
      BuySL.Selected(false);
      return true;
     }
   if(name==SellSL.Name())
     {
      StopLoss_pips.Text(DoubleToString(MathAbs(SellSL.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_BID))/_Point,0));
      SLPipsEndEdit();
      SellSL.Selected(false);
      return true;
     }
   if(name==BuyTP.Name())
     {
      TakeProfit_pips.Text(DoubleToString(MathAbs(BuyTP.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_ASK))/_Point,0));
      TPPipsEndEdit();
      BuyTP.Selected(false);
      return true;
     }
   if(name==SellTP.Name())
     {
      TakeProfit_pips.Text(DoubleToString(MathAbs(SellTP.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_BID))/_Point,0));
      TPPipsEndEdit();
      SellTP.Selected(false);
      return true;
     }
   return false;
  }

6. 再起動時の現在のパラメータの保存

プログラムの再起動を行う時、きっとユーザーは全ての数値をまたパネルに入力したくはないなと思うはずです。確かに、多くの場合、ユーザーにとって使いやすい場所まで、毎回パネルを移動するのは多くの人をイライラさせます。これを我慢できる人もいるとは思いますが、このアクションはターミナルを再起動した時にのみ必要なものにしたいと思います。しかし、プログラムの再起動は、チャートのタイムフレームの簡単な変更をした時に起こるということを忘れないでおきましょう。これは遥かに頻繁に発生します。されに、多くの取引システムは複数のタイムフレームでのチャートの研究を求めます。その為、私達にはオプションボタンとチェックボックスの状態の保存、またユーザーが手動で入力した全ての欄の数値を保存する必要があります。そして勿論、パネルはウィンドウの位置と状態も記憶する必要があります。

最後のアクションについては、すでに親クラスで実装されています。プログラムの起動時の、保存情報の読み込みを実装するだけです。

編集可能欄とボタンの状態については、少し頑張る必要があります。とはいっても、作業の多くの部分はすでに開発者の皆さんによって実行されているので、彼らに感謝しています。

遠くの継承クラスにまで行くことはありませんが、最も前の先祖クラスCObjectから始まり、全ての継承クラスはSaveとLoad関数を持っているということに言及しておきます。私達のCTradePanelクラスは、自分の親クラスからクラスの初期化が行われない時に、全ての含有オブジェクトを保存する関数の呼び出しを継承しました。しかしながら、私達を嬉しくないサプライズが待っていて、CEditとCBmpButtonのクラスは『空の』関数を継承しました。

   //--- methods for working with files
   virtual bool      Save(const int file_handle)                         { return(true);   }
   virtual bool      Load(const int file_handle)                         { return(true);   }
したがって、私達は保存したいオブジェクトのデータの為に、これらの関数を書き換える必要があります。この目的から、それぞれCEditとCBmpButtonクラスの子孫となる2つの新しいクラスーCEdit_newとCBmpButton_newを作成します。その中にデータの読み込みと保存の関数を書いていきます。
class CEdit_new : public CEdit
  {
public:
                     CEdit_new(void){};
                    ~CEdit_new(void){};
   virtual bool      Save(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      string text=Text();
      FileWriteInteger(file_handle,StringLen(text));
      return(FileWriteString(file_handle,text)>0); 
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      int size=FileReadInteger(file_handle);
      string text=FileReadString(file_handle,size);
      return(Text(text));
     }
   
  };

class CBmpButton_new : public CBmpButton
  {
public:
                     CBmpButton_new(void){};
                    ~CBmpButton_new(void){};
   virtual bool      Save(const int file_handle)
    {
     if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      return(FileWriteInteger(file_handle,Pressed()));
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      return(Pressed((bool)FileReadInteger(file_handle)));
     }
  };

そして勿論、保存するオブジェクトタイプを新しいものに変更します。

   CEdit_new         Lots;                            // Display volume of next order
   CEdit_new         StopLoss_pips;                   // Display Stop loss in pips
   CEdit_new         StopLoss_money;                  // Display Stop loss in accaunt currency
   CEdit_new         TakeProfit_pips;                 // Display Take profit in pips
   CEdit_new         TakeProfit_money;                // Display Take profit in account currency
   CEdit_new         Risk_percent;                    // Display Risk percent to equity
   CEdit_new         Risk_money;                      // Display Risk in account currency
   CBmpButton_new    StopLoss_line;                   // Check to display StopLoss Line
   CBmpButton_new    TakeProfit_line;                 // Check to display TakeProfit Line
   CBmpButton_new    StopLoss_pips_b;                 // Select Stop loss in pips
   CBmpButton_new    StopLoss_money_b;                // Select Stop loss in accaunt currency
   CBmpButton_new    TakeProfit_pips_b;               // Select Take profit in pips
   CBmpButton_new    TakeProfit_money_b;              // Select Take profit in account currency
   CBmpButton_new    Risk_percent_b;                  // Select Risk percent to equity
   CBmpButton_new    Risk_money_b;                    // Select Risk in account currency

しかし、まだ情報を読み込む必要があるので、保存するものは少ないです。この為に、私達の取引パネルを起動させる関数を書きます。

public:
.................
   virtual bool      Run(void);

始めに保存したデータを読み込みます。

//+------------------------------------------------------------------+
//| Run of Trade Panel                                               |
//+------------------------------------------------------------------+
bool CTradePanel::Run(void)
  {
   IniFileLoad();

それから変数値を更新します。

   cur_lot=StringToDouble(Lots.Text());
   cur_sl_pips=(int)StringToInteger(StopLoss_pips.Text());     // Stop Loss in pips
   cur_sl_money=StringToDouble(StopLoss_money.Text());         // Stop Loss in money
   cur_tp_pips=(int)StringToInteger(TakeProfit_pips.Text());   // Take Profit in pips
   cur_tp_money=StringToDouble(TakeProfit_money.Text());       // Take Profit in money
   cur_risk_percent=StringToDouble(Risk_percent.Text());       // Risk in percent
   cur_risk_money=StringToDouble(Risk_money.Text());           // Risk in money
   RiskByValue=true;
そして最後に、ストップロスとテイクプロフィットレベルの状態をアクチュアルにするチェックボックスを押す処理関数を呼び出しましょう。
   StopLossLineClick();
   TakeProfitLineClick();
   return(CAppDialog::Run());
  }

7. 『全般の掃除』

私達は大きな仕事をし、ユーザーが満足していることを願います。しかし、何かしらの理由でユーザーが私達のプログラムをオフにすることが起こります。そして、去り際に私達は後片付けをしなければいけません。私達が作成した全てのオブジェクトをチャートから削除しますが、この時ユーザーまたは第三者のプログラムによって作成されたオブジェクトは残します。

プログラムの非初期化の際に、非初期化の理由を示すOnDeinit関数を呼び出すDeinitイベントが生成されます。その為、主要プログラムの指定した関数から、私達のクラスの非初期化関数を呼び出す必要があります。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   TradePanel.Destroy(reason);
   return;
  }

この関数は、私達のクラスのpublicブロックで宣言する必要があります。

public:
.............
   virtual void      Destroy(const int reason);
この関数の本体では、チャートからの水平線を削除して、全ての必要な情報を保存し、チャートから取引パネルのオブジェクトを削除する親クラスの非初期化関数を呼び出します。
//+------------------------------------------------------------------+
//| Application deinitialization function                            |
//+------------------------------------------------------------------+
void CTradePanel::Destroy(const int reason)
  {
   BuySL.Delete();
   SellSL.Delete();
   BuyTP.Delete();
   SellTP.Delete();
   CAppDialog::Destroy(reason);
   return;
  }

まとめ

親愛なる読者、仲間、友人の皆さん!

私は皆さんがこの記事を最後まで読み終えたことを願いつつ、この記事が皆さんにとって有益なものであったことを願います。

ここでは、取引パネル作成の経験を使える言葉でご紹介し、市場での作業の為の出来上がったツールを提供しました。

私達の取引パネルでどんなものが見たいかなど、あなたのアイディアや希望を是非教えてください。その代わりに、私は最も面白いアイディアを具現化し、今後の記事でその事についてご紹介することをお約束します。

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

添付されたファイル |
tradepanel.ex5 (319.32 KB)
tradepanel.mq5 (56.85 KB)
トレーダーの為の正規表現 トレーダーの為の正規表現
正規表現(英語ではregular expressions) とは、正規表現のパターンやマスクと呼ばれる指定されたルールに従ったテキストを処理する為の特別な言語です。この記事では、MQL5のRegularExpressionsライブラリを使用した取引レポートの処理をご紹介し、それを使った最適化結果をデモンストレーションします。
グラフィカルインタフェース V:リストビュー要素(チャプター 2) グラフィカルインタフェース V:リストビュー要素(チャプター 2)
前の章では、縦横スクロールバーを作成するためのクラスを書きました。この章では、それを実装します。縦スクロールバーはそのコンパウンドの一部となるリストビュー要素を作成するためのクラスが記述されます。
マーケット用の任意の非標準チャートのインディケータを作成するには マーケット用の任意の非標準チャートのインディケータを作成するには
オフラインチャートとMQL4言語でのプログラミングによって、任意のタイプのチャートを獲得することができます。(『三目並べ』、『練行足』、『カギ』、『レンジバー』、等量チャートなど)本稿ではDLLを使用せずにこれを行う方法を紹介します。したがって、このような"2in1"のインディケータをマーケットで公開したり、購入することができます。
グラフィカルインタフェース V: 縦横のスクロールバー(チャプター 1) グラフィカルインタフェース V: 縦横のスクロールバー(チャプター 1)
MetaTrader環境でのグラフィカルインタフェースを作成するためのライブラリの開発の検討が続きます。シリーズの第一部の最初の記事では、縦横のスクロールバーを作成するためのクラスを作成します。