【MQL4】サンプルEAのMoving Average.mq4をリファクタリングしてみよう
こんにちは。もるふぉと申します。外資系IT会社のプログラマです。
今まで、PHP, Ruby, Python, Java, C++, GoLang などを利用して自社、他社問わず様々な企業のソフトウェアを構築してきました。
EAは自分用のものを作成してきました。
エンジニアリングの観点から皆様のお力になれるのではないかと思いEAに関する情報発信していきます。
サンプルEAのMoving Average.mq4を読みやすくしてみよう
以前の記事で読みやすいコードを細切れで紹介させていただきました。
今回はMT4をインストールした時にデフォルトでバンドルされているサンプルEAのMoving Average.mq4 はサンプルEAの割にはプログラム的にツッコミどころがいくつかあるので、これを教材としてリファクタリングしてみたいと思います。
※以前の記事はこちら↓
🗺️ リファクタリングの方針
アーリーリターンの導入:ネストを浅くし、条件を満たさない場合は早期にリターンすることで可読性を向上させる。
不要なコメントの削除:不要なコメントを削除し、コードが簡潔になるようにする
ループと条件文の簡略化:ループ内の条件文を簡略化し、不要なネストを減らす。
とします。
📖 Moving Average.mq4全コード
//+------------------------------------------------------------------+
//| Moving Average.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2005-2014, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
#property description "Moving Average sample expert advisor"
#define MAGICMA 20131111
//--- Inputs
input double Lots =0.1;
input double MaximumRisk =0.02;
input double DecreaseFactor=3;
input int MovingPeriod =12;
input int MovingShift =6;
//+------------------------------------------------------------------+
//| Calculate open positions |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
int buys=0,sells=0;
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
{
if(OrderType()==OP_BUY) buys++;
if(OrderType()==OP_SELL) sells++;
}
}
//--- return orders volume
if(buys>0) return(buys);
else return(-sells);
}
//+------------------------------------------------------------------+
//| Calculate optimal lot size |
//+------------------------------------------------------------------+
double LotsOptimized()
{
double lot=Lots;
int orders=HistoryTotal(); // history orders total
int losses=0; // number of losses orders without a break
//--- select lot size
lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//--- calcuulate number of losses orders without a break
if(DecreaseFactor>0)
{
for(int i=orders-1;i>=0;i--)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
{
Print("Error in history!");
break;
}
if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
continue;
//---
if(OrderProfit()>0) break;
if(OrderProfit()<0) losses++;
}
if(losses>1)
lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
}
//--- return lot size
if(lot<0.1) lot=0.1;
return(lot);
}
//+------------------------------------------------------------------+
//| Check for open order conditions |
//+------------------------------------------------------------------+
void CheckForOpen()
{
double ma;
int res;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
if(Open[1]>ma && Close[1]<ma)
{
res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
return;
}
//--- buy conditions
if(Open[1]<ma && Close[1]>ma)
{
res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
return;
}
//---
}
//+------------------------------------------------------------------+
//| Check for close order conditions |
//+------------------------------------------------------------------+
void CheckForClose()
{
double ma;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
//--- check order type
if(OrderType()==OP_BUY)
{
if(Open[1]>ma && Close[1]<ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
if(OrderType()==OP_SELL)
{
if(Open[1]<ma && Close[1]>ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
}
//---
}
//+------------------------------------------------------------------+
//| OnTick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- check for history and trading
if(Bars<100 || IsTradeAllowed()==false)
return;
//--- calculate open orders by current symbol
if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
else CheckForClose();
//---
}
//+------------------------------------------------------------------+
それぞれ関数ごとにリファクタリングしていきましょう。
📚 int CalculateCurrentOrders(string symbol)
☑️ オリジナルコード
int CalculateCurrentOrders(string symbol)
{
int buys=0,sells=0;
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
{
if(OrderType()==OP_BUY) buys++;
if(OrderType()==OP_SELL) sells++;
}
}
//--- return orders volume
if(buys>0) return(buys);
else return(-sells);
}
✅ リファクタリングしたコード
int CalculateCurrentOrders(string symbol) {
int buys = 0, sells = 0;
for (int i = 0; i < OrdersTotal(); i++) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) break;
if (OrderSymbol() != Symbol() || OrderMagicNumber() != MAGICMA) continue;
if (OrderType() == OP_BUY) buys++;
if (OrderType() == OP_SELL) sells++;
}
return (buys > 0) ? buys : -sells;
}
アーリーリターンを使い不要なネストを削除しました。
📚 double LotsOptimized()
☑️ オリジナルコード
double LotsOptimized()
{
double lot=Lots;
int orders=HistoryTotal(); // history orders total
int losses=0; // number of losses orders without a break
//--- select lot size
lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//--- calcuulate number of losses orders without a break
if(DecreaseFactor>0)
{
for(int i=orders-1;i>=0;i--)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
{
Print("Error in history!");
break;
}
if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
continue;
//---
if(OrderProfit()>0) break;
if(OrderProfit()<0) losses++;
}
if(losses>1)
lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
}
//--- return lot size
if(lot<0.1) lot=0.1;
return(lot);
}
✅ リファクタリングしたコード
double LotsOptimized() {
double lot = NormalizeDouble(AccountFreeMargin() * MaximumRisk / 1000.0, 1);
int losses = 0;
if (DecreaseFactor > 0) {
for (int i = HistoryTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
Print("Error in history!");
break;
}
if (OrderSymbol() != Symbol() || OrderType() > OP_SELL) continue;
if (OrderProfit() > 0) break;
if (OrderProfit() < 0) losses++;
}
if (losses > 1) lot = NormalizeDouble(lot - lot * losses / DecreaseFactor, 1);
}
return (lot < 0.1) ? 0.1 : lot;
}
不要な変数、ネストを削除しました。
📚 void CheckForOpen()
☑️ オリジナルコード
void CheckForOpen()
{
double ma;
int res;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
if(Open[1]>ma && Close[1]<ma)
{
res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
return;
}
//--- buy conditions
if(Open[1]<ma && Close[1]>ma)
{
res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
return;
}
//---
}
✅ リファクタリングしたコード
void CheckForOpen() {
if (Volume[0] > 1) return;
double ma = iMA(NULL, 0, MovingPeriod, MovingShift, MODE_SMA, PRICE_CLOSE, 0);
int res;
if (Open[1] > ma && Close[1] < ma) {
res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
if (res < 0) {
Print("OrderSend OP_SELL failed with error #", GetLastError());
}
} else if (Open[1] < ma && Close[1] > ma) {
res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
if (res < 0) {
Print("OrderSend OP_BUY failed with error #", GetLastError());
}
}
}
不要な変数の削除しました。エラーハンドリングしました。
📚 void CheckForClose()
☑️ オリジナルコード
void CheckForClose()
{
double ma;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
//--- check order type
if(OrderType()==OP_BUY)
{
if(Open[1]>ma && Close[1]<ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
if(OrderType()==OP_SELL)
{
if(Open[1]<ma && Close[1]>ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
}
//---
}
✅ リファクタリングしたコード
void CheckForClose() {
if (Volume[0] > 1) return;
double ma = iMA(NULL, 0, MovingPeriod, MovingShift, MODE_SMA, PRICE_CLOSE, 0);
for (int i = 0; i < OrdersTotal(); i++) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) break;
if (OrderMagicNumber() != MAGICMA || OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUY && Open[1] > ma && Close[1] < ma) {
if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White)) {
Print("OrderClose error ", GetLastError());
}
break;
} else if (OrderType() == OP_SELL && Open[1] < ma && Close[1] > ma) {
if (!OrderClose(OrderTicket(), OrderLots(), Ask, 3, White)) {
Print("OrderClose error ", GetLastError());
}
break;
}
}
}
不要な変数を削除、ネストを削除しました。
👨🔧 Moving Average.mq4のバグを修正する
ここまでEAの挙動を変えないよう修正してきました。
実はMoving Average.mq4にバグが存在します。
if (Volume[0] > 1) return;
CheckForClose関数やCheckForOpen関数で見かけるというコードは時間足の始値で実行するという意図で書かれていると思います。
しかし、これが高頻度のティックによるスパイクやラグを裁けない場合があります。
MQL4の入門書にも頻出する書き方ですが誤りです。注意してください。
🔧 バグの修正方法
void OnTick() {
static datetime lastBarTime = 0;
if (Bars < 100 || !IsTradeAllowed()) return;
datetime currentBarTime = iTime(Symbol(), 0, 0);
if (currentBarTime == lastBarTime) return;
lastBarTime = currentBarTime;
if (CalculateCurrentOrders(Symbol()) == 0) {
CheckForOpen();
} else {
CheckForClose();
}
}
static 変数の導入:OnTick 関数内に static datetime lastBarTime を導入しました。これにより、lastBarTime の値は関数が呼び出されるたびに保持されます。
バーの開始時間の確認:currentBarTime と lastBarTime を比較し、currentBarTime が lastBarTime と異なる場合のみ処理を続行します。
CheckForClose関数やCheckForOpen関数 からはif (Volume[0] > 1) return;を削除してください
🍜 まとめ
今回のリファクタリングでは、コードの可読性とメンテナンス性を向上させるためにいくつかの改善を行いました。
具体的には、ネストの削減、アーリーリターンの導入、変数のスコープの適切な制限、そしてエラーハンドリングの強化を実施しました。
これにより、コードの構造が明確になり、将来的な変更や拡張が容易になりました。
また、適切なエラーチェックを行うことで、運用時の予期せぬエラーを減少させ、システムの信頼性を高めることができました。
リファクタリングは単なるコードの整理ではなく、品質向上のための重要なステップです。
🍖おまけ
リファクタリングしたコード全文を500円で提供します。
といっても文中にでてきたコードをつなぎ合わせれば出来上がりますので実質投げ銭になります🙇♂️
ここから先は
¥ 500
Amazonギフトカード5,000円分が当たる
この記事が気に入ったらチップで応援してみませんか?