FXオートトレードプログラムを開発する会(10)

前回のリンク
次回のリンク

スポンサーリンク

カスタムインディケータをEAに組み込む

今回は,カスタムインディケータをEA(エキスパートアドバイザ)に組み込む方法を紹介します.

具体的には,移動平均に基づいたEAであるサンプルプログラムのMoving Average.mt5に対して,それと等価なEAをカスタムインディケータ組み込むことで実装することを目指します.つまり,移動平均値を参照するために使用しているテクニカルインディケータ関数iMAの代わりに移動平均を計算するカスタムインディケータであるCustom Moving Average.mq5をiCustom関数で読み込むことを考えます.Custom Moving Average.mq5もMQL5のサンプルプログラムで,フォルダ構成を変更していなければ”MQL5/Indicators/Examples”にあるはずです.内容は以下の通り.

//+----------------------------------------------------------------+
//|                                      Custom Moving Average.mq5 |
//|                 Copyright 2009-2020, MetaQuotes Software Corp. |
//|                                            http://www.mql5.com |
//+----------------------------------------------------------------+
#property copyright "2009-2020, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"

//--- indicator settings
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  Red
//--- input parameters
input int            InpMAPeriod=13;         // Period
input int            InpMAShift=0;           // Shift
input ENUM_MA_METHOD InpMAMethod=MODE_SMMA;  // Method
//--- indicator buffer
double ExtLineBuffer[];
//+----------------------------------------------------------------+
//| Custom indicator initialization function                       |
//+----------------------------------------------------------------+
void OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
//--- set accuracy
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//--- set first bar from what index will be drawn
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod);
//--- line shifts when drawing
   PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift);
//--- name for DataWindow
   string short_name;
   switch(InpMAMethod)
     {
      case MODE_EMA :
         short_name="EMA";
         break;
      case MODE_LWMA :
         short_name="LWMA";
         break;
      case MODE_SMA :
         short_name="SMA";
         break;
      case MODE_SMMA :
         short_name="SMMA";
         break;
      default :
         short_name="unknown ma";
     }
   IndicatorSetString(INDICATOR_SHORTNAME,short_name+"("+string(InpMAPeriod)+")");
//--- set drawing line empty value
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
  }
//+----------------------------------------------------------------+
//|  Moving Average                                                |
//+----------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   if(rates_total<InpMAPeriod-1+begin)
      return(0);
//--- first calculation or number of bars was changed
   if(prev_calculated==0)
     {
      ArrayInitialize(ExtLineBuffer,0);
      PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod-1+begin);
     }
//--- calculation
   switch(InpMAMethod)
     {
      case MODE_EMA:
         CalculateEMA(rates_total,prev_calculated,begin,price);
         break;
      case MODE_LWMA:
         CalculateLWMA(rates_total,prev_calculated,begin,price);
         break;
      case MODE_SMMA:
         CalculateSmoothedMA(rates_total,prev_calculated,begin,price);
         break;
      case MODE_SMA:
         CalculateSimpleMA(rates_total,prev_calculated,begin,price);
         break;
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+----------------------------------------------------------------+
//|   simple moving average                                        |
//+------------------------------------------------------------------+
void CalculateSimpleMA(int rates_total,int prev_calculated,int begin,const double &price[])
  {
   int i,start;
//--- first calculation or number of bars was changed
   if(prev_calculated==0)
     {
      start=InpMAPeriod+begin;
      //--- set empty value for first start bars
      for(i=0; i<start-1; i++)
         ExtLineBuffer[i]=0.0;
      //--- calculate first visible value
      double first_value=0;
      for(i=begin; i<start; i++)
         first_value+=price[i];
      first_value/=InpMAPeriod;
      ExtLineBuffer[start-1]=first_value;
     }
   else
      start=prev_calculated-1;
//--- main loop
   for(i=start; i<rates_total && !IsStopped(); i++)
      ExtLineBuffer[i]=ExtLineBuffer[i-1]+(price[i]-price[i-InpMAPeriod])/InpMAPeriod;
  }
//+----------------------------------------------------------------+
//|  exponential moving average                                    |
//+----------------------------------------------------------------+
void CalculateEMA(int rates_total,int prev_calculated,int begin,const double &price[])
  {
   int    i,start;
   double SmoothFactor=2.0/(1.0+InpMAPeriod);
//--- first calculation or number of bars was changed
   if(prev_calculated==0)
     {
      start=InpMAPeriod+begin;
      ExtLineBuffer[begin]=price[begin];
      for(i=begin+1; i<start; i++)
         ExtLineBuffer[i]=price[i]*SmoothFactor+ExtLineBuffer[i-1]*(1.0-SmoothFactor);
     }
   else
      start=prev_calculated-1;
//--- main loop
   for(i=start; i<rates_total && !IsStopped(); i++)
      ExtLineBuffer[i]=price[i]*SmoothFactor+ExtLineBuffer[i-1]*(1.0-SmoothFactor);
  }
//+----------------------------------------------------------------+
//|  linear weighted moving average                                |
//+----------------------------------------------------------------+
void CalculateLWMA(int rates_total,int prev_calculated,int begin,const double &price[])
  {
   int    weight=0;
   int    i,l,start;
   double sum=0.0,lsum=0.0;
//--- first calculation or number of bars was changed
   if(prev_calculated<=InpMAPeriod+begin+2)
     {
      start=InpMAPeriod+begin;
      //--- set empty value for first start bars
      for(i=0; i<start; i++)
         ExtLineBuffer[i]=0.0;
     }
   else
      start=prev_calculated-1;

   for(i=start-InpMAPeriod,l=1; i<start; i++,l++)
     {
      sum   +=price[i]*l;
      lsum  +=price[i];
      weight+=l;
     }
   ExtLineBuffer[start-1]=sum/weight;
//--- main loop
   for(i=start; i<rates_total && !IsStopped(); i++)
     {
      sum             =sum-lsum+price[i]*InpMAPeriod;
      lsum            =lsum-price[i-InpMAPeriod]+price[i];
      ExtLineBuffer[i]=sum/weight;
     }
  }
//+----------------------------------------------------------------+
//|  smoothed moving average                                       |
//+----------------------------------------------------------------+
void CalculateSmoothedMA(int rates_total,int prev_calculated,int begin,const double &price[])
  {
   int i,start;
//--- first calculation or number of bars was changed
   if(prev_calculated==0)
     {
      start=InpMAPeriod+begin;
      //--- set empty value for first start bars
      for(i=0; i<start-1; i++)
         ExtLineBuffer[i]=0.0;
      //--- calculate first visible value
      double first_value=0;
      for(i=begin; i<start; i++)
         first_value+=price[i];
      first_value/=InpMAPeriod;
      ExtLineBuffer[start-1]=first_value;
     }
   else
      start=prev_calculated-1;
//--- main loop
   for(i=start; i<rates_total && !IsStopped(); i++)
      ExtLineBuffer[i]=(ExtLineBuffer[i-1]*(InpMAPeriod-1)+price[i])/InpMAPeriod;
  }
//+----------------------------------------------------------------+


単純移動平均(Simple moving average)だけでなく,指数移動平均(Exponential moving average)や線形加重移動平均(Linear weighted moving average)など,様々な種類の移動平均の計算が定義されていることがわかります.すなわち,関数を呼び出すときに移動平均を計算するためのパラメータだけでなく,移動平均の種類を指定することで期待する移動平均値を参照することができるようになっています.(ちなみに,この点は実はiMA関数と同じです.)

では,このような移動平均値を計算するカスタムインディケータをiCustom関数を使ってEAであるMoving Average.mq5に組み込んでみましょう.最終的なコードは次のようになります.

//+------------------------------------------------------------------+
//|                                              MA_with_iCustom.mq5 |
//|                                        Copyright 2021, Sciotein. |
//|                                             http://sciotein.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, Sciotein."
#property link      "http://sciotein.com/"
#property version   "1.00"

#include <Trade\Trade.mqh>

input double MaximumRisk        = 0.02;    // Maximum Risk in percentage
input double DecreaseFactor     = 3;       // Descrease factor
input int    MovingPeriod       = 12;      // Moving Average period
input int    MovingShift        = 6;       // Moving Average shift
//---
int    ExtHandle=0;
bool   ExtHedging=false;
CTrade ExtTrade;

#define MA_MAGIC 1234501
//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double TradeSizeOptimized(void)
  {
   double price=0.0;
   double margin=0.0;
//--- select lot size
   if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price))
      return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin))
      return(0.0);
   if(margin<=0.0)
      return(0.0);

   double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_MARGIN_FREE)*MaximumRisk/margin,2);
//--- calculate number of losses orders without a break
   if(DecreaseFactor>0)
     {
      //--- select history for access
      HistorySelect(0,TimeCurrent());
      //---
      int    orders=HistoryDealsTotal();  // total history deals
      int    losses=0;                    // number of losses orders without a break

      for(int i=orders-1; i>=0; i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket==0)
           {
            Print("HistoryDealGetTicket failed, no trade history");
            break;
           }
         //--- check symbol
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol)
            continue;
         //--- check Expert Magic number
         if(HistoryDealGetInteger(ticket,DEAL_MAGIC)!=MA_MAGIC)
            continue;
         //--- check profit
         double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
         if(profit>0.0)
            break;
         if(profit<0.0)
            losses++;
        }
      //---
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- normalize and check limits
   double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lot=stepvol*NormalizeDouble(lot/stepvol,0);

   double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol)
      lot=minvol;

   double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol)
      lot=maxvol;
//--- return trading volume
   return(lot);
  }
//+------------------------------------------------------------------+
//| Check for open position conditions                               |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- go trading only for first ticks of new bar
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- get current Moving Average
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- check signals
   ENUM_ORDER_TYPE signal=WRONG_VALUE;

   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_SELL;    // sell conditions
   else
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_BUY;  // buy conditions
     }
//--- additional checking
   if(signal!=WRONG_VALUE)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         ExtTrade.PositionOpen(_Symbol,signal,TradeSizeOptimized(),
                               SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK),
                               0,0);
     }
//---
  }
//+------------------------------------------------------------------+
//| Check for close position conditions                              |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- go trading only for first ticks of new bar
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- get current Moving Average
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- positions already selected before
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);

   if(type==(long)POSITION_TYPE_BUY && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
   if(type==(long)POSITION_TYPE_SELL && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- additional checking
   if(signal)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         ExtTrade.PositionClose(_Symbol,3);
     }
//---
  }
//+------------------------------------------------------------------+
//| Position select depending on netting or hedging                  |
//+------------------------------------------------------------------+
bool SelectPosition()
  {
   bool res=false;
//--- check position in Hedging mode
   if(ExtHedging)
     {
      uint total=PositionsTotal();
      for(uint i=0; i<total; i++)
        {
         string position_symbol=PositionGetSymbol(i);
         if(_Symbol==position_symbol && MA_MAGIC==PositionGetInteger(POSITION_MAGIC))
           {
            res=true;
            break;
           }
        }
     }
//--- check position in Netting mode
   else
     {
      if(!PositionSelect(_Symbol))
         return(false);
      else
         return(PositionGetInteger(POSITION_MAGIC)==MA_MAGIC); //---check Magic number
     }
//--- result for Hedging mode
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- prepare trade class to control positions if hedging mode is active
   ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   ExtTrade.SetExpertMagicNumber(MA_MAGIC);
   ExtTrade.SetMarginMode();
   ExtTrade.SetTypeFillingBySymbol(Symbol());
//--- Moving Average indicator by iMA
//   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
//   if(ExtHandle==INVALID_HANDLE)
//     {
//      printf("Error creating MA indicator");
//      return(INIT_FAILED);
//     }
//--- Moveing Average indicator by Custom Moving Average.mq5 with iCustom
   ExtHandle = iCustom(_Symbol,_Period,"Examples\\Custom Moving Average",MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("Error creating MA indicator");
      return(INIT_FAILED);
     }
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(SelectPosition())
      CheckForClose();
   else
      CheckForOpen();
//---
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+

変更したのはOnInit関数の中身だけです.すなわち,変更点のみを並べると次のようになります.

//--- Moving Average indicator by iMA
//   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
//   if(ExtHandle==INVALID_HANDLE)
//     {
//      printf("Error creating MA indicator");
//      return(INIT_FAILED);
//     }
//--- Moveing Average indicator by Custom Moving Average.mq5 with iCustom
   ExtHandle = iCustom(_Symbol,_Period,"Examples\\Custom Moving Average",MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("Error creating MA indicator");
      return(INIT_FAILED);
     }

コメントアウトしてある部分が元のコードで,iMA関数を使って移動平均値を参照しています.新しく書き加えたコメントアウトしていない部分では,iCustom関数でCustom Moving Average.mq5を呼び出すことでiMA関数を用いた場合と等価なコードを実現しています.ちなみに,ここでは移動平均の種類として”MODE_SMA”を指定しており,単純移動平均を計算しています.

これをコンパイルし,ストラテジーテスターで実データに対して動かしてみると,次のようになります.

確かに,尤もらしい動きをしていることがわかります.

念のため,元のサンプルプログラムMoving Average.mq5を同じデータに適用した場合の結果を次に示します.

それぞれの結果を比較すると,iCustom関数およびCustom Moving Average.mq5を用いて移動平均値を得たEAとiMA関数を用いて移動平均値を得たEAが全く同じ動作をしていることを確認できました.すなわち当初の目的を達成できたといえるでしょう.

簡単にはなりましたが,実例を交えてカスタムインディケータをEAに組み込む方法を紹介しました.

皆さまの参考になれば幸いです.

次回のリンクはこちら

コメント

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