MQLでEAを開発する際、コードの構造化は非常に重要です。今回は、MT4で提供される2つのサンプルEAを例に、代表的なコーディングスタイルとその使い分けについて解説します。
2つのコーディングスタイル
1. モノリシック(一体型)プログラミング
MACD Sample.mq4で採用されているスタイルです。すべてのロジックを主にOnTick()関数内に直接記述する方法です。
//+------------------------------------------------------------------+
//| MACD Sample.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2005-2014, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
input double TakeProfit =50;
input double Lots =0.1;
input double TrailingStop =30;
input double MACDOpenLevel =3;
input double MACDCloseLevel=2;
input int MATrendPeriod =26;
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnTick(void)
{
double MacdCurrent,MacdPrevious;
double SignalCurrent,SignalPrevious;
double MaCurrent,MaPrevious;
int cnt,ticket,total;
//---
// initial data checks
// it is important to make sure that the expert works with a normal
// chart and the user did not make any mistakes setting external
// variables (Lots, StopLoss, TakeProfit,
// TrailingStop) in our case, we check TakeProfit
// on a chart of less than 100 bars
//---
if(Bars<100)
{
Print("bars less than 100");
return;
}
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return;
}
//--- to simplify the coding and speed up access data are put into internal variables
MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
total=OrdersTotal();
if(total<1)
{
//--- no opened orders identified
if(AccountFreeMargin()<(1000*Lots))
{
Print("We have no money. Free Margin = ",AccountFreeMargin());
return;
}
//--- check for long position (BUY) possibility
if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("BUY order opened : ",OrderOpenPrice());
}
else
Print("Error opening BUY order : ",GetLastError());
return;
}
//--- check for short position (SELL) possibility
if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,"macd sample",16384,0,Red);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("SELL order opened : ",OrderOpenPrice());
}
else
Print("Error opening SELL order : ",GetLastError());
}
//--- exit from the "no opened orders" block
return;
}
//--- it is important to enter the market correctly, but it is more important to exit it correctly...
for(cnt=0;cnt<total;cnt++)
{
if(!OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES))
continue;
if(OrderType()<=OP_SELL && // check for opened position
OrderSymbol()==Symbol()) // check for symbol
{
//--- long position is opened
if(OrderType()==OP_BUY)
{
//--- should it be closed?
if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
MacdCurrent>(MACDCloseLevel*Point))
{
//--- close order and exit
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet))
Print("OrderClose error ",GetLastError());
return;
}
//--- check for trailing stop
if(TrailingStop>0)
{
if(Bid-OrderOpenPrice()>Point*TrailingStop)
{
if(OrderStopLoss()<Bid-Point*TrailingStop)
{
//--- modify order and exit
if(!OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green))
Print("OrderModify error ",GetLastError());
return;
}
}
}
}
else // go to short position
{
//--- should it be closed?
if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
{
//--- close order and exit
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet))
Print("OrderClose error ",GetLastError());
return;
}
//--- check for trailing stop
if(TrailingStop>0)
{
if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
{
if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
{
//--- modify order and exit
if(!OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red))
Print("OrderModify error ",GetLastError());
return;
}
}
}
}
}
}
//---
}
//+------------------------------------------------------------------+
メリット
- 処理の流れが分かりやすい
- 開発の初期段階で素早く実装できる
- デバッグが比較的容易
- 小規模なプロジェクトに適している
デメリット
- コードの再利用が難しい
- 機能の追加・修正が困難になりやすい
- プログラムが大きくなると保守が大変
- チーム開発には向かない
2. モジュラー(関数分割型)プログラミング
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();
//---
}
//+------------------------------------------------------------------+
メリット
- コードの再利用性が高い
- 保守が容易
- 機能の追加・修正が簡単
- チーム開発に適している
- テストが容易
- コードの可読性が高い
デメリット
- 初期の開発時間が長くなる
- 適切な関数分割の設計スキルが必要
- わずかなパフォーマンスのオーバーヘッド
WAN
この2つのサンプルEAにはそれぞれ、コーディングスタイルが違うという意味があったんだ(‘Д’)
よく使われるシーンと使い分け
モノリシック型が適している場合
- 簡単なインジケーターの作成
- 一時的な検証用スクリプト
- 小規模な個人用EA
- プロトタイプの作成
- 処理速度が極めて重要な場合
モジュラー型が適している場合
- 複雑なトレードロジックを持つEA
- チームでの開発プロジェクト
- 長期的なメンテナンスが必要なEA
- 複数のEAで共通の機能を使い回す場合
- テスト駆動開発を行う場合
モノリシック型が一般的な開発分野
1. スクリプト言語での開発
- Python/Ruby/PHP での小規模スクリプト
- データ処理スクリプト
- 自動化ツール
- 簡単なユーティリティプログラム
2. フロントエンド開発(小〜中規模)
- シンプルなWebアプリケーション
- 単一ページのアプリケーション(初期段階)
- ランディングページ
- プロトタイプ開発
3. データ分析・機械学習
- Jupyter Notebook での分析
- データ探索
- モデル検証
- 可視化スクリプト
4. システム管理
- シェルスクリプト
- バックアップスクリプト
- システムモニタリング
- 簡単な自動化タスク
モジュラー型が一般的な開発分野
1. エンタープライズアプリケーション
- Java/C#での開発
- 業務システム
- ERPシステム
- バンキングシステム
2. バックエンド開発
- 各種サーバーサイド開発
- RESTful API
- マイクロサービス
- データベース連携システム
3. 大規模フロントエンド開発
- モダンフレームワークを使用した開発
- React/Vue.jsの大規模アプリケーション
- Angularプロジェクト
- エンタープライズWebアプリケーション
4. 組み込みシステム開発
- C/C++での開発
- 車載システム
- 産業機器制御
- IoTデバイス
WAN
C言語系のMQLはモジュラー型のコーディングが適しているよ!
まとめ
初心者の方は、まずモノリシック型で基本を学び、その後モジュラー型に移行することをお勧めします。これにより、
- 最初は処理の流れを理解しやすい
- 徐々にコードの構造化の重要性を実感できる
- 段階的にプログラミングスキルを向上させられる
ただし、商用EAの開発や長期的なプロジェクトでは、最初からモジュラー型で設計することを強く推奨します。将来の保守性や拡張性を考慮すると、初期の開発時間が多少かかっても、モジュラー型のメリットの方が大きいためです。