カスタムインディケータを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に組み込む方法を紹介しました.
皆さまの参考になれば幸いです.
次回のリンクはこちら.
コメント