移動平均線を用いたEAは、MetaTrader 5の標準パッケージに含まれており、移動平均線インジケーターを使って取引を行うEAの一例です。
このEAファイル「Moving Average.mq5」は、「terminal_data_folder\MQL5\Experts\Examples\Moving Average\」フォルダーに格納されています。このEAは、テクニカルインジケーター、取引履歴関数、そして標準ライブラリの取引クラスを使用した例です。また、このEAには取引結果に基づいた資金管理システムが組み込まれています。
では、Expert Advisorの構造とその動作について考えてみましょう。
1. EAのプロパティ
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2009-2013, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00"
最初の5行はコメントで、次の3行はMQL5プログラムのプロパティ(著作権、リンク、バージョン)をプリプロセッサディレクティブ#propertyを使って設定しています。
EAを実行すると、これらの情報は「共通」タブに表示されます:

図1. 移動平均線EAの共通パラメータ
1.2. インクルードファイル
次に、#includeディレクティブがコンパイラに「Trade.mqh」ファイルを含めるよう指示します。
このファイルは標準ライブラリの一部で、取引関数への簡単なアクセスのためにCTradeクラスが含まれています。
#include <Trade\Trade.mqh>インクルードファイルの名前は<>の中に表示され、パスは「terminal_data_folder\Include\」ディレクトリに相対的に設定されています。
1.3. 入力パラメータ
次に、タイプ、名前、デフォルト値、コメントが続きます。これらの役割は図2に示されています。
input double MaximumRisk = 0.02; // 最大リスク(パーセンテージ) input double DecreaseFactor = 3; // 減少係数 input int MovingPeriod = 12; // 移動平均期間 input int MovingShift = 6; // 移動平均シフト
MaximumRiskとDecreaseFactorパラメータは資金管理に使用され、MovingPeriodとMovingShiftは移動平均線インジケーターの期間とシフトを設定します。これらは取引条件を確認するために使用されます。
入力パラメータの行にあるコメントのテキストは、デフォルト値とともに「オプション」タブに表示されます。

図2. 移動平均線EAの入力パラメータ
1.4. グローバル変数
次に、グローバル変数ExtHandleが宣言されます。これは移動平均線インジケーターのハンドルを保存するために使用されます。
//--- int ExtHandle=0;
続いて、6つの関数があります。各関数の目的は関数本体の前にあるコメントで説明されています:
- TradeSizeOptimized() - 最適なロットサイズを計算する;
- CheckForOpen() - ポジションオープン条件をチェックする;
- CheckForClose() - ポジションクローズ条件をチェックする;
- OnInit() - EA初期化関数;
- OnTick() - EAティック関数;
- OnDeinit() - EA非初期化関数;
最後の3つの関数はイベント処理関数で、最初の3つのサービス関数はそのコード内で呼び出されます。
2. イベント処理関数
2.1. OnInit()初期化関数
OnInit()関数は、Expert Advisorの最初の起動時に1回呼び出されます。通常、OnInit()イベントハンドラでは、EAの動作準備が行われます:入力パラメータがチェックされ、インジケーターとパラメータが初期化されます。重大なエラーが発生した場合、EAのさらなる操作が無意味になるため、関数はINIT_FAILEDの戻りコードで終了します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("MAインジケーターの作成エラー"); return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); }
EAの取引は移動平均線インジケーターに基づいているため、iMA()を呼び出すことで、移動平均線インジケーターが作成され、そのハンドルがグローバル変数ExtHandleに保存されます。
エラーが発生した場合、OnInit()はINIT_FAILEDの戻りコードで終了します。これは、EA/インジケーターの操作を正常に完了させる正しい方法です。
2.2. OnTick()関数
OnTick()関数は、EAが実行されているチャートのシンボルに新しいクォートが受信されるたびに呼び出されます。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(PositionSelect(_Symbol)) CheckForClose(); else CheckForOpen(); //--- }
PositionSelect()関数は、現在のシンボルに対して開いているポジションがあるかどうかを判断します。
開いているポジションがあれば、CheckForClose()関数が呼び出され、現在の市場の状態を分析して開いているポジションを閉じます。そうでなければ、CheckForOpen()が呼び出され、市場へのエントリー条件をチェックし、その条件が発生した場合には新しいポジションを開きます。
2.3. OnDeInit()非初期化関数
OnDeInit()は、EAがチャートから削除されると呼び出されます。プログラムが操作中にグラフィカルオブジェクトを配置する場合、これらはチャートから削除できます。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+
この場合、Expert Advisorの非初期化中には何のアクションも実行されません。
3. サービス関数
3.1. TradeSizeOptimized()関数
この関数は、指定されたリスクレベルと取引結果に基づいてポジションオープンのための最適なロットサイズを計算して返します。
//+------------------------------------------------------------------+ //| 最適なロットサイズを計算 | //+------------------------------------------------------------------+ double TradeSizeOptimized(void) { double price=0.0; double margin=0.0; //--- ロットサイズを計算 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_FREEMARGIN)*MaximumRisk/margin,2); //--- 連続して損失した取引の系列の長さを計算 if(DecreaseFactor>0) { //--- 取引履歴全体のリクエスト HistorySelect(0,TimeCurrent()); //-- int orders=HistoryDealsTotal(); // 取引の総数 int losses=0; // シリーズ内の損失取引の数 for(int i=orders-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) { Print("HistoryDealGetTicket失敗、取引履歴なし"); break; } //--- 取引シンボルの確認 if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol) continue; //--- 利益の確認 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); } //--- 取引量の許可される値を正規化し、チェック 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(lot); }
SymbolInfoDouble()関数は、現在のシンボルの価格が利用可能かどうかを確認するために使用され、その後、OrderCalcMargin()関数を使用して、注文を出すために必要なマージンをリクエストします(この場合は買い注文)。初期のロットサイズは、注文に必要なマージンの値、アカウントの自由マージン(AccountInfoDouble(ACCOUNT_FREEMARGIN))及び入力パラメータMaximumRiskで指定された最大リスクの値から決定されます。
入力パラメータDecreaseFactorの値が正の場合、履歴の取引を分析し、ロットサイズを調整します。初期ロットサイズは、サイズ(1-losses/DecreaseFactor)で掛け算されます。
その後、取引ボリュームは、現在のシンボルの最小許容ボリュームのステップ(stepvol)の整数倍に「丸め」ます。また、取引ボリュームの最小(minvol)と最大(maxvol)値もリクエストし、ロット値が許可された限界を超えた場合は調整されます。その結果、この関数は計算された取引ボリュームの値を返します。
3.2. CheckForOpen()関数
CheckForOpen()は、ポジションオープン条件をチェックし、取引条件が発生した場合(この場合は価格が移動平均線を越えたとき)にポジションを開きます。
//+------------------------------------------------------------------+ //| ポジションオープン条件のチェック | //+------------------------------------------------------------------+ void CheckForOpen(void) { MqlRates rt[2]; //--- 価格値をコピー if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRatesの",_Symbol,"失敗、履歴なし"); return; } //--- 新しいバーの最初のティックでのみ取引する if(rt[1].tick_volume>1) return; //--- 移動平均線インジケーターの現在の値を取得 double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBufferからiMA失敗、データなし"); return; } //--- 信号をチェック ENUM_ORDER_TYPE signal=WRONG_VALUE; if(rt[0].open>ma[0] && rt[0].close<ma[0]) signal=ORDER_TYPE_SELL // 売り条件 else { if(rt[0].open<ma[0] && rt[0].close>ma[0]) signal=ORDER_TYPE_BUY // 買い条件 } //--- 追加のチェック if(signal!=WRONG_VALUE) if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) if(Bars(_Symbol,_Period)>100) { CTrade trade; trade.PositionOpen(_Symbol,signal,TradeSizeOptimized(), SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK), 0,0); } //--- }
移動平均線を使用した取引では、価格が移動平均線を越えるかどうかをチェックする必要があります。CopyRates()関数を使用して、現在の価格の2つの値が構造体rt[]にコピーされ、rt[1]は現在のバーに、rt[0]は完了したバーに対応します。
新しいバーは、現在のバーのティックボリュームが1であるかどうかを確認し、もしそうであれば新しいバーが始まります。この新しいバーの形成を検出する方法には失敗する場合があるため(クォートがパックで来る場合など)、新しいバーの形成の事実は現在のクォートの時間を保存して比較することで行うべきです。
移動平均線インジケーターの現在の値は、CopyBuffer()関数を使用して要求され、ma[]配列に保存されます。このプログラムは、価格が移動平均線を越えたかどうかをチェックし、追加のチェック(EAの取引が可能かどうか、履歴にバーが存在するかどうか)を行います。成功した場合、取引オブジェクト(CTradeのインスタンス)のPositionOpen()メソッドを呼び出してシンボルに対して適切なポジションを開きます。
ポジション開設価格は、信号変数の値に応じて、SymbolInfoDouble()関数を使用してBidまたはAsk価格が設定されます。ポジションボリュームは上記で説明したTradeSizeOptimized()を呼び出すことで決定されます。
3.3. CheckForClose()関数
CheckForClose()は、ポジションクローズの条件をチェックし、その条件が発生した場合にポジションを閉じます。
//+------------------------------------------------------------------+ //| ポジションクローズ条件のチェック | //+------------------------------------------------------------------+ void CheckForClose(void) { MqlRates rt[2]; //--- 価格値をコピー if(CopyRates(_Symbol,_Period,0,2,rt)!=2) { Print("CopyRatesの",_Symbol,"失敗、履歴なし"); return; } //--- 新しいバーの最初のティックでのみ取引する if(rt[1].tick_volume>1) return; //--- 移動平均線インジケーターの現在の値を取得 double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBufferからiMA失敗、データなし"); return; } //--- 先に選択したポジションのタイプを取得 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; //--- 追加のチェック if(signal) if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) if(Bars(_Symbol,_Period)>100) { CTrade trade; trade.PositionClose(_Symbol,3); } //--- }
CheckForClose()関数のアルゴリズムは、CheckForOpen()のアルゴリズムに似ています。現在のオープンポジションの方向に応じて、クローズ条件(移動平均線を下回る買いポジション、上回る売りポジション)がチェックされます。オープンポジションは、取引オブジェクト(CTradeのインスタンス)のPositionClose()メソッドを呼び出すことでクローズされます。
4. バックテスト
最適なパラメータの値は、ストラテジーテスターを使用して見つけることができます。
たとえば、MovingPeriodパラメータを2012年1月1日から2013年8月1日までの間で最適化すると、最良の結果はMovingPeriod=45で得られます:

移動平均線Expert Advisorのバックテスト結果
結論
MetaTrader 5の標準パッケージに含まれる移動平均線Expert Advisorは、テクニカルインジケーター、取引履歴関数、そして取引クラスの使用例です。さらに、このEAには取引結果に基づいた資金管理システムが組み込まれています。