MQL5 EA講座 第75回「EAにクラスで作った関数を実装し改良する」【MT5用EAの作り方】

MQL5でEA作ろう講座

前回は OriginalCTradeクラス に以下の関数を追加しました。

それぞれの関数の処理実装記述も再掲しておきましょう。

マジックナンバーを返す関数

// マジックナンバーを設定する
void OriginalCTrade::SetMagicNumber(ulong parMagic)
{
	magicNumber = parMagic;
}

スリッページを返す関数

// スリッページを設定する
void OriginalCTrade::SetDeviation(ulong parDeviation)
{
	deviation = parDeviation;
}

フィルポリシーを返す関数

// フィルポリシーを設定する
void OriginalCTrade::SetFillType(ENUM_ORDER_TYPE_FILLING parFill)
{
	fillType = parFill;
}

まだまだ、実践に使うには色々と物足りないですが、ここまでの段階で新規に成行注文を出す分には差し支えがなくなったのではないかな、と思います。

今回は第68回「簡単な仕組みのMT5用EAを作るーその1ー」第69回「簡単な仕組みのMT5用EAを作るーその2ー」を通して作ったMT5用シンプルEAに、改良を施していきたいと思います。

スポンサーリンク
スポンサーリンク

OriginalTradeファイルをメインプログラムで使う為include命令を記述する

OriginalTrade.mqhファイルをメインプログラムで使う為には、include命令を記述する必要があります。

#property copyright "MQL5ssei"
#property link      "https://mqlinvestmentlab.com/"
#property version   "1.00"

#include <OriginalTrade.mqh>

これで、OriginalTrade.mqhファイルで作ったクラス関数が使えるようになりました。

include命令については↓の記事をご覧ください。

第56回「#include命令(#include directive)」

OriginalCTradeのインスタンスを生成する。

include命令により、OriginalTrade.mqhファイル内にあるクラス関数が使えるようになったところで、次はOriginalCTradeクラスインスタンスを生成します。インスタンス名を「Trade」とします。

//OriginalTrade.mqhファイルを呼び出す
#include <OriginalTrade.mqh>
//トレード用のクラス「OriginalCTrade」のインスタンスを生成する
OriginalCTrade Trade;

インスタンス「Trade」を生成したことにより、OriginalCTradeクラス内のアクセスレベルpublic関数が使えるようになりました。

インスタンスの生成方法については↓の記事をご覧ください。

MQL5 EA講座 第49回「クラスについて2 -クラスの使い方-」

OnInit関数内にマジックナンバーを返す関数を呼び出す

マジックナンバーは一度設定すれば、その後基本的には変えることのない情報です。従って、OnTick関数にて呼び出す必要はありません。

OnInit関数内に、前回作ったOriginalCTradeクラスメンバであるSetMagicNumber関数を呼び出します。

講座記事第68回「簡単な仕組みのMT5用EAを作るーその1ー」の中の、「ステップ2:デモ口座でしか機能しない回路を盛り込む」という項目で、OnInit関数内にデモ口座でしか機能しないような記述を行いました。

その下にマジックナンバーを返す関数を呼び出してみましょう↓

 ENUM_ACCOUNT_TRADE_MODE tradeMode=(ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE);
   if(tradeMode != ACCOUNT_TRADE_MODE_DEMO)                 // デモ口座以外の場合
     {

      MessageBox("このEAはデモ口座でのみ稼働します","口座エラー");
      return INIT_FAILED;                            // 処理終了
     }

//マジックナンバーを返す関数を呼び出す
     Trade.SetMagicNumber(Magic);

SetMagicNumber関数引数には、input変数の「Magic」を記述します。

SetMagicNumber関数は、アクセスレベルprotected変数「magicNumber」にマジックナンバー代入する関数です。

「magicNumber」はPositionOpen関数が使われるときにMqlTradeRequest構造体メンバ変数である.magicに値を代入します。

SetMagicNumber関数の呼び出しにより、マジックナンバーの設定が滞りなく完了しました。

OnInit関数については↓の記事をご覧ください。

MQL5 EA講座 第5回「OnInit関数」

input変数については↓の記事をご覧ください。

MQL5 EA講座 第24回「Input変数」

OnInit関数内にスリッページを返す関数を呼び出す

スリッページも一度設定すれば、その後基本的には変えることのない情報です。OnInit関数内に、前回作ったOriginalCTradeクラスメンバであるSetDeviation関数を呼び出します。

 //マジックナンバーを返す関数を呼び出す
     Trade.SetMagicNumber(Magic);
     //スリッページを返す関数を呼び出す
     Trade.SetDeviation(Deviation);

SetDeviation関数は、アクセスレベルprotected変数「deviation」にスリッページ代入する関数です。「deviation」はPositionOpen関数が使われるときにMqlTradeRequest構造体メンバ変数である.deviationに値を代入します。

SetDeviation関数の呼び出しにより、スリッページの設定は終了です。

OnInit関数内にフィルポリシーを返す関数を呼び出す

フィルポリシーについてですが、

講座記事第68回「簡単な仕組みのMT5用EAを作るーその1ー」の中の、

フィルポリシーを設定する関数を作り、メンバ変数.type_fillingに代入する」という項目で、

適切なフィルポリシーを返す関数というのをメインプログラムのグローバル領域に作りました。

メインプログラムはなるべく記述を少なくしたいので、この関数OriginalTrade.mqhファイルに移動させます。↓

※この時、メインプログラム内に宣言した関数を残したまま、インクルードファイルにその関数を移し、include命令によって当該mqhファイルを読み込ませようとすると、関数を2重に定義しているとみなされ、

function already defined and has body 関数は既に定義され実体を持っています)

というコンパイルエラーが出るので注意しましょう。↓

フィルポリシーについても一度設定すれば、その後基本的には変えることのない情報です。OnInit関数内に、まず適切なフィルポリシーを格納する変数「filltype」を宣言し、適切なフィルポリシーを返す関数FillPolicy()の戻り値を格納させます。

そして、前回作ったOriginalCTradeクラスメンバであるSetDeviation関数を呼び出します。引数には変数「filltype」を記述します。

これでフィルポリシーの設定は完了です。

//適切なフィルポリシーを格納する変数を宣言
     ENUM_ORDER_TYPE_FILLING filltype=FillPolicy();

     //マジックナンバーを返す関数を呼び出す
     Trade.SetMagicNumber(Magic);
     //スリッページを返す関数を呼び出す
     Trade.SetDeviation(Deviation);
     //フィルポリシーを設定する関数を呼び出す
     Trade.SetFillType(filltype);

続いてOnTick関数内の改良に入ります。

OnTick関数内の改良

OnTick関数内の改良といっても、現状マジックナンバーを返す関数スリッページを返す関数フィルポリシーを返す関数OnInit関数内で利用し、現時点で他にOriginalCTradeクラスで実装しているメンバ関数は、Buy関数Sell関数だけです。

改良できる箇所はそれほど多くありません。

買い注文の改良1 -OrderSend関数をBuy関数に置き換える-

まずは買い注文の部分。

今はこうなっていますが↓

買い注文を出す
      request.action = TRADE_ACTION_DEAL;
      request.type = ORDER_TYPE_BUY;
      request.symbol = _Symbol;
      request.position = 0;
      request.volume = Volume;
      request.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      request.sl = 0;
      request.tp = 0;
      request.deviation = Deviation;

      bool sent = OrderSend(request,result);

これを↓のように変えます。

//買い注文を出す
buyPosNum=Trade.Buy(_Symbol,Volume);

大分スッキリしましたね。

Buy関数は新規成行買い注文を行う際の大方の設定を関数内で設定しているので、銘柄名とロット数さえ指定すれば買い注文が行えます(必要であれば、ストップロスとテイクプロフィット、コメントを引数で設定する事も可能です)

「buyPosNum」はポジション番号を格納する為の変数でした(詳細は講座記事第69回内の「買いポジションと売りポジションのポジション番号を格納する変数を宣言」をご覧ください)。

Buy関数は、ポジション番号(=成行注文時のオーダー番号)を戻り値として返しますから、それを受け取る変数としてbuyPosNumを使っています。

MqlTradeRequest構造体メンバ変数~OrderSend関数の部分を/**/で囲みコメントアウトした上で、Buy関数に置き換えましょう。(動作チェックをして問題なければ後でコメントアウト部分は削除します)↓

買い注文の改良2 -ストップロスとテイクプロフィットの設定部分を変更する-

Buy関数により、注文が成功した場合、変数「buyPosNum」には0以外の数字(ポジション番号)が入ります。まずストップロスとテイクプロフィットの設定部分の冒頭条件文を

// ストップロスとテイクプロフィットを設定する
      if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)

から↓の

if(buyPosNum>0)

に変更します。ポジション番号を格納しているbuyPosNumが0じゃない事で約定成立を意味します

そして、request.position代入する値、およびPositionSelectByTicket関数引数として記述されている値をresult.orderからbuyPosNumに変更します。

↓の部分が・・・

// ストップロスとテイクプロフィットを設定する
      if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)
        {
         request.action = TRADE_ACTION_SLTP;
         request.position = result.order;

         PositionSelectByTicket(result.order);

↓に変わります

     // ストップロスとテイクプロフィットを設定する
      if(buyPosNum>0)
        {
         request.action = TRADE_ACTION_SLTP;
         request.position = buyPosNum;

         PositionSelectByTicket(buyPosNum);

動画で見てみると・・・↓

これまで、MqlTradeResult構造リターンコードポジション番号取得の手段として使っていたのを、今後はBuy関数戻り値に置き換えようという訳です。

現時点での買いポジション箇所の変更は以上です。続いて売りポジション箇所の変更です。

売り注文の改良1 -OrderSend関数をSell関数に置き換える-

Sell関数は新規成行売り注文を行う際の大方の設定を、関数内で設定しているので、銘柄名とロット数さえ指定すれば売り注文が行えます(必要であれば、ストップロスとテイクプロフィット、コメントを引数で設定する事も可能です)

「sellPosNum」はポジション番号を格納する為の変数になっています

Sell関数は、ポジション番号(=成行注文時のオーダー番号)を戻り値として返しますから、それを受け取る変数としてsellPosNumを使っています。

買い注文の時と同様に、MqlTradeRequest構造体メンバ変数~OrderSend関数の部分を/**/で囲みコメントアウトした上で、Sell関数に置き換えます。(こちらも動作チェックをして問題なければ後でコメントアウト部分は削除します)↓

売り注文の改良2 -ストップロスとテイクプロフィットの設定部分を変更する-

Sell関数により、注文が成功した場合、変数「sellPosNum」には0以外の数字(ポジション番号)が入ります。ストップロスとテイクプロフィットの設定部分の冒頭条件文を

// ストップロスとテイクプロフィットを設定する
      if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)

から↓の

if(sellPosNum>0)

に変更します。ポジション番号を格納しているsellPosNumが0じゃない事で約定成立を意味します

そして、

そして、request.position代入する値、およびPositionSelectByTicket関数引数として記述されている値をresult.orderからsellPosNumに変更します。

↓の部分が・・・

// ストップロスとテイクプロフィットを設定する
      if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)
        {
         request.action = TRADE_ACTION_SLTP;
         request.position = result.order;

         PositionSelectByTicket(result.order);

↓に変わります

// ストップロスとテイクプロフィットを設定する
      if(sellPosNum>0)
        {
         request.action = TRADE_ACTION_SLTP;
         request.position = sellPosNum;

         PositionSelectByTicket(sellPosNum);

動画にするとこんな感じです↓

これまで、MqlTradeResult構造リターンコードポジション番号取得の手段として使っていたのを、今後はSell関数戻り値に置き換えました。

売り注文の修正も以上になります。バックテストEAを動かしてみると↓

問題なく動いているようなので、買い注文、売り注文それぞれの箇所で/**/で囲みコメントアウトしたMqlTradeRequest構造体メンバ変数~OrderSend関数の部分は削除します。

コード全体の記述内容

コード全体の記述内容は以下の通りです。

//+------------------------------------------------------------------+
//|                                                      testEA5.mq5 |
//|                                                         MQL5ssei |
//|                                    https://mqlinvestmentlab.com/ |
//+------------------------------------------------------------------+
#property copyright "MQL5ssei"
#property link      "https://mqlinvestmentlab.com/"
#property version   "1.00"

//OriginalTrade.mqhファイルを呼び出す
#include <OriginalTrade.mqh>
//トレード用のクラス「OriginalCTrade」のインスタンスを生成する
OriginalCTrade Trade;

//必要なパラメータ
input double Volume=0.1;//ロット数
input int SL=200;//ストップロス
input int TP=200;//テイクプロフィット
input int MAPeriod = 20;//移動平均期間
input int Deviation=50;//スリッページ
input int Magic=1234;//マジックナンバー

//直近の注文状況をチェックするグローバル変数
bool BuyPosition, SellPosition;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

   ENUM_ACCOUNT_TRADE_MODE tradeMode=(ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE);
   if(tradeMode != ACCOUNT_TRADE_MODE_DEMO)                 // デモ口座以外の場合
     {

      MessageBox("このEAはデモ口座でのみ稼働します","口座エラー");
      return INIT_FAILED;                            // 処理終了
     }

//適切なフィルポリシーを格納する変数を宣言
   ENUM_ORDER_TYPE_FILLING filltype=FillPolicy();

//マジックナンバーを返す関数を呼び出す
   Trade.SetMagicNumber(Magic);
//スリッページを返す関数を呼び出す
   Trade.SetDeviation(Deviation);
//フィルポリシーを設定する関数を呼び出す
   Trade.SetFillType(filltype);
//---
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//インスタンスの生成
   MqlTradeRequest request;
   MqlTradeResult result;

//構造体の初期化
   ZeroMemory(request);
// ZeroMemory(result);

//フィルポリシーを設定する。
   request.type_filling=FillPolicy();

// 単純移動平均の値取得
//単純移動平均の値を格納する配列宣言
   double sma[];

//配列を時系列にセット
   ArraySetAsSeries(sma,true);

//単純移動平均のハンドルを取得
   int smaHandle=iMA(_Symbol,0,MAPeriod,MODE_SMA,0,PRICE_CLOSE);
///単純移動平均の値を配列にコピー
   CopyBuffer(smaHandle,0,0,1,sma);


//終値情報を変数に格納
   double close=iClose(NULL,0,0);

// ポジション状況の確認とポジション番号の取得
//買いポジションと売りポジションのチケット番号を格納する変数を宣言
   ulong buyPosNum = 0, sellPosNum = 0;
   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticketNum = PositionGetTicket(i);
      PositionSelectByTicket(ticketNum);

      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
        {
         buyPosNum = ticketNum;
         BuyPosition = true;

        }//if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)

      else
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
           {
            sellPosNum = ticketNum;
            SellPosition = true;

           }// else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)

     }//for(int i = 0; i < PositionsTotal(); i++)

// 買いの注文に関する記述
   if(close> sma[0] && BuyPosition == false)
     {
      // 売りポジションがあれば決済する。
      if(sellPosNum > 0)
        {
         PositionSelectByTicket(sellPosNum);

         request.action = TRADE_ACTION_DEAL;
         request.type = ORDER_TYPE_BUY;
         request.symbol = _Symbol;
         request.position = sellPosNum;
         request.volume = PositionGetDouble(POSITION_VOLUME);
         request.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         request.deviation = Deviation;

         bool sent = OrderSend(request, result);
        }

      // 買い注文を出す
      buyPosNum=Trade.Buy(_Symbol,Volume);


      // ストップロスとテイクプロフィットを設定する
      if(buyPosNum>0)
        {
         request.action = TRADE_ACTION_SLTP;
         request.position = buyPosNum;

         PositionSelectByTicket(buyPosNum);
         double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);

         if(SL > 0)
            request.sl = positionOpenPrice - (SL * _Point);
         if(TP > 0)
            request.tp = positionOpenPrice + (TP * _Point);

         if(request.sl > 0 || request.tp > 0)
            bool sent = OrderSend(request,result);

         SellPosition = false;
        }
     }


//売り注文に関する記述
   if(close <sma[0] && SellPosition== false)
     {
      // 買いポジションがあれば決済する。
      if(buyPosNum > 0)
        {
         PositionSelectByTicket(buyPosNum);

         request.action = TRADE_ACTION_DEAL;
         request.type = ORDER_TYPE_SELL;
         request.symbol = _Symbol;
         request.position = buyPosNum;
         request.volume = PositionGetDouble(POSITION_VOLUME);
         request.price = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         request.deviation = Deviation;

         bool sent = OrderSend(request, result);
        }//if(buyPosNum > 0)


      // 売り注文を出す
      sellPosNum=Trade.Sell(_Symbol,Volume);

      // ストップロスとテイクプロフィットを設定する
      if(sellPosNum>0)
        {
         request.action = TRADE_ACTION_SLTP;
         request.position = sellPosNum;

         PositionSelectByTicket(sellPosNum);
         double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);

         if(SL > 0)
            request.sl = positionOpenPrice + (SL * _Point);
         if(TP > 0)
            request.tp = positionOpenPrice - (TP * _Point);

         if(request.sl > 0 || request.tp > 0)
            bool sent = OrderSend(request,result);

         BuyPosition = false;
        }

     }// if(close <ma[0] && SellPosition== false)


  }//OnTick

//+------------------------------------------------------------------+

現時点では決済や、SLTP設定については引き続き、OrderSend関数及びMqlTradeRequest構造体メンバ変数を使った注文記述がなされています。

今後、OriginalCTradeクラスに決済処理を担う関数、やSLTP設定の処理を担う関数も実装していく予定です。

そして、実装したら再度EAのコードも改修していきます。

OrderSend関数及びMqlTradeRequest構造体メンバ変数を使っている処理に関しては、OriginalCTradeクラスとの紐づけがないので、OnInit関数内に記述した、メンバ関数フィルポリシーを返す関数も効果を発揮しません。そのため現状では、OnTick関数内にFillPolicy関数を残している状態です。

本日は一旦以上とさせていただきます。

最後までお読みいただきありがとうございました<m(__)m>

第74回「マジックナンバー・スリッページ・フィルポリシーを返す関数を実装」

    →【超入門】MQL5 EA講座 第76回「SLとTPを設定する関数をクラスに追加する」

コメント

タイトルとURLをコピーしました