作るもの
MQL5で、ごく単純なルールで取引するEAを作ってみます。(稼げるルールではない)
エントリーとエグジットのルール、および、EA実行時に指定できるパラメータは以下のとおり。
ロング条件・内容
- 足確定時に、ローソク足が単純移動平均線を上抜けていたらエントリー (ショートは逆)
- 足確定時に、ローソク足が単純移動平均線を下抜けていたらエグジット (ショートは逆)
- ロット数は固定
- エントリー時に固定幅で指値・逆指値決済注文を入れる
- ポジションは1つしか持たない
パラメータ
- 移動平均線の期間
- 決済指値・逆指値幅
- ロット数
- 許容スリッページ幅
参考サイト
空のコードの用意
最初なので、ウィザードを使用して空のコードを用意する手順を示しておきます。
※ 画像をクリックすると、大きく表示されます。

ツールバーの 新規作成 をクリック。

エキスパートアドバイザ (テンプレート) を選択し、次へ をクリック。

ファイル名を入力する。
初期状態で Experts が入力されているので、その後ろにファイル名を入力する。
左の例では、Experts の下に Hailu フォルダを作成し、その中に Sample1 というファイルが作成される。
著作権、リンクはお好みで。
パラメータは、今回は空にしておく。
次へ をクリック。

イベントハンドラを選択する。(その1)
全部チェックをつけてもいい。
今回は OnTrade だけチェックを付けておく。
なお、ここでチェックを付けなかったイベントも、コードに記述すれば動いてくれる。
次へ をクリック。

イベントハンドラを選択する。(その2)
完了 をクリック。
すると、いくつか空のイベントハンドラが書かれたコードが表示されるので、このコードを変更していきます。
実装
※ このページの末尾に、コード全体を記載しています。
パラメータ
<実装例>
input uint MaPeriod = 20;
input uint Lots = 1;
input uint LimitPoints = 1000;
input uint StopPoints = 300;
input ulong AllowableSlipPoints = 20;
頭に input を付けたグローバル変数(input変数)は、EA実行時に設定できるパラメータになります。
なお、input変数は、プログラム内では変更できないとのこと。
やろうと思わないでしょうけど、いちおう。
グローバル変数・定数
<実装例>
int hMA = 0; // MAインジケータのハンドル
int status = 0; // 0:未エントリー / 1:ロングポジション保有 / 2:ショートポジション保有
datetime prevBarDt; // 1つ前のローソク足の開始時刻
ENUM_ORDER_TYPE_FILLING fillMode; // 執行条件
const ulong magic = 1; // このEAのID
この例で使用するグローバル変数と定数です。
グローバル変数はちょっと...
という人は、MQL5にも namespace がありますので、適当にググってみてください。
class や static修飾子もありますが、MQL5 の static が他の言語の static と同じような動きになるかどうかは、私は確認していません。
この辺りも、気になる人はググるなり試すなりしてみてください。このサイトでもそのうち扱うかもしれません。
イベントハンドラ
使用するイベントハンドラは、以下の3つです。
イベントハンドラ | 実行されるタイミング |
OnInit | EAが起動し、グローバル変数が初期化された後 |
OnTick | 値動きがあったとき |
OnTrade | 何かしらの取引がサーバーで完了したとき |
OnInit()
<実装例>
int OnInit()
{
// 執行条件を選択
SelectFillType();
// MAインジケータを作成
hMA = iMA(_Symbol, _Period, MaPeriod, 0, MODE_SMA, PRICE_CLOSE);
if (hMA == INVALID_HANDLE)
{
PrintFormat("Failed to create MA indicators. [%d]", GetLastError());
return INIT_FAILED;
}
// 1つ前のローソク足の時刻を初期化
prevBarDt = iTime(_Symbol, _Period, 0);
return INIT_SUCCEEDED;
}
【 執行条件を選択 】
注文の執行条件を選択します。
執行条件については後述します。
【 インジケータを生成 】
移動平均線などのインジケータを使用したい場合、OnInit() でインジケータを生成しておきます。
移動平均線の場合は、iMA関数を使用します。
MQL開発者はアップル信者⁉
と一瞬思ったけど、indicator の i ですね。たぶん。
iMA関数の定義は以下のとおり。
int iMA(
string symbol, // 銘柄名
ENUM_TIMEFRAMES period, // 時間軸
int ma_period, // 平均期間
int ma_shift, // 水平シフト
ENUM_MA_METHOD ma_method, // 平滑化の種類
ENUM_APPLIED_PRICE applied_price // 価格の種類かハンドル
);
戻り値はインジケータのハンドル。
エラー発生時は INVALID_HANDLE 定数を返す。
この例では、symbol(銘柄名) に _Symbol、period(時間軸) に _Period という定義済み変数を指定しています。
これらの変数には、EAを実行しているチャートの銘柄名と時間軸が設定されています。
大抵はこれらを使えばいいと思いますが、特定の時間軸の EMA を使いたいなどのときには、period に PERIOD_H1(1時間足) などの定数を指定します。
MetaEditorで編集していれば、時間軸の定数は「PE」くらいまで入力すれば候補が表示されると思います。
ma_period には、MAの期間を指定します。
この例では input変数の MaPeriod を指定しています。
ma_shift は、公式ドキュメントには「Shift of the indicator relative to the price chart.」と記載されています。
よく分かりませんが、チャート上にローソク足とズラしてMAを描画するときに使用するようです。
今回は使いません。0(ゼロ)を指定します。
ma_method は、MAの種類を指定します。
この例では MODE_SMA を指定しています。
ご想像のとおり、EMAを使用したい場合は MODE_EMA です。
MAの種類の定数も、「MO」あたりまで入力すれば、候補が表示されると思います。
applied_price は、ローソク足のどの値の平均を取るかを指定します。
この例では PRICE_CLOSE(終値) を指定しています。
この定数は、「PRI」くらいまで入力すれば候補が絞られて、選択しやすくなると思います。
【 その他 】
この例では 1つ前のローソク足の時刻 を初期化しています。
これは、後述する足確定の判定に使用します。
OnTick()
<実装例>
void OnTick()
{
// 現在のローソク足の開始時刻を取得
datetime dt = iTime(_Symbol, _Period, 0);
if (dt == 0)
{
PrintFormat("Failed to get time. [%d]", GetLastError());
return;
}
// 足確定
if (dt != prevBarDt)
{
if (status > 0)
CheckExit();
else
CheckEnter();
prevBarDt = dt;
}
}
足の確定を確認し、後述するエントリー・エグジットの処理を呼んでいます。
足が確定したかどうかは、iTime関数で現在の(確定していない)ローソク足の開始時刻を取得し、前回の OnTick() からローソク足の開始時刻が変わったかどうかで判断しています。
iTime関数の定義は以下のとおり。
datetime iTime(
const string symbol, // 銘柄
ENUM_TIMEFRAMES timeframe, // 期間
int shift // シフト
);
今回は時刻が変わったかどうかしか確認しないため、datetime型の詳細は調べていません。
日付や時刻などの数値を取得したい場合は、MqlDateTime型という構造体に変換するようです。
きっと他の言語と同様に、datetime型は、内部的には double型あたりなのでしょう。しらんけど。
なお、OnTick() は値が動いたときに呼ばれるので、この方法だと 値動きが少ないときに判定が遅れる可能性 がありますが、
そこはもう諦めます!
メジャー通貨ペアならほとんど気にならないと信じてます!
ここでがんばる必要もなさそうだし。
OnTrade()
<実装例>
void OnTrade()
{
if (PositionsTotal() <= 0)
status = 0;
}
この例ではポジションの保有状態をグローバル変数で管理しているので、ポジションが決済されて無くなっていれば、状態を初期化するようにしています。
エグジット処理で初期化すると、指値・逆指値で自動決済されたときに初期化できないので、OnTrade() で初期化するようにしています。
PositionsTotal関数は、読んで字のごとく、ポジション数を取得する関数です。
エントリー
次の手順で処理します。
- ローソク足を取得
- 移動平均値を取得
- エントリー条件を満たしていれば、エントリー
以下に詳細を記載します。
ローソク足の取得
<実装例>
// ローソク足を取得
MqlRates rt[2];
if (CopyRates(_Symbol, _Period, 0, 2, rt) != 2)
{
PrintFormat("Failed to get history data. [%d]", GetLastError());
return;
}
ローソク足は CopyRates関数で取得します。
CopyRates関数は3つの呼び出し方(引数の取り方)がありますが、ここでは1つだけ紹介します。
CopyRates関数の定義は以下のとおり。
int CopyRates(
string symbol_name, // 銘柄名
ENUM_TIMEFRAMES timeframe, // 時間軸
int start_pos, // 開始位置
int count, // 複製するデータ数
MqlRates rates_array[] // 受け取り側の配列
);
戻り値は、取得できたローソク足の数。
エラー発生時は -1 を返す。
symbol_name には銘柄名を指定します。
この例では、_Symbol変数を指定しています。
timeframe には時間軸を指定します。
この例では、_Period変数を指定しています。
start_pos と count は、取得するローソク足の開始位置と個数です。
ここで、start_pos は、最新のローソク足 (確定していない足) が 0(ゼロ)、1つ前の足が1、… になります。
rates_array は MqlRates構造体の配列で、ローソク足の格納先です。
古いローソク足から順に格納されます。
MqlRates構造体の定義は以下のとおり。
struct MqlRates
{
datetime time; // 期間開始時刻
double open; // 始値
double high; // 期間中の最高値
double low; // 期間中の最安値
double close; // 終値
long tick_volume; // ティックボリューム
int spread; // スプレッド
long real_volume; // 取引高
};
スプレッド、取引高といった気になる情報も含まれていますが、今回は使いません。
使うのは始値と終値だけです。
rates_array に格納されるデータの順序が分かりにくいので、以下に例を示します。

例えば上のチャートでは、
- start_pos = 0 , count = 5 ➡ rates_array[0] = ④ , rates_array[1] = ⑤ , … , rates_array[4] = ⑧
- start_pos = 1 , count = 2 ➡ rates_array[0] = ⑥ , rates_array[1] = ⑦
上の実装例では start_pos = 0, count = 2 としていますが、実は start_pos = 1, count = 1 で十分です。
移動平均値の取得
<実装例>
// MAを取得
double ma[2];
if (CopyBuffer(hMA, 0, 0, 2, ma) != 2)
{
PrintFormat("Failed to get MA values. [%d]", GetLastError());
return;
}
移動平均値は CopyBuffer関数で取得します。
CopyBuffer関数は3つの呼び出し方(引数の取り方)がありますが、ここでは1つだけ紹介します。
CopyBuffer関数の定義は以下のとおり。
int CopyBuffer(
int indicator_handle, // 指標ハンドル
int buffer_num, // 指標バッファ番号
int start_pos, // 開始位置
int count, // 複製する量
double buffer[] // 受け取り側の配列
);
戻り値は、取得できたデータの数。
エラー発生時は -1 を返す。
indicator_handle には、インジケータのハンドルを指定します。
この例では、OnInit() で作成した MA のハンドルを指定しています。
buffer_num は、今回は使いません。0(ゼロ)を指定します。
カスタム指標で使うようです。
start_pos と count は、CopyRates関数と同様です。
buffer に移動平均値が格納されます。
注文条件の判定 ~ 成行の新規買い・売り注文
<実装例>
// 上に抜けた
if ((rt[0].open <= ma[0]) && (rt[0].close > ma[0]))
{
// 注文
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = Lots;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.sl = req.price - StopPoints * _Point;
req.tp = req.price + LimitPoints * _Point;
req.deviation = AllowableSlipPoints;
req.type = ORDER_TYPE_BUY;
req.type_filling = fillMode;
req.magic = magic;
if (OrderSend(req, res))
{
Print(res.comment);
if (res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Failed to send an order. [%d] : %s", GetLastError(), res.comment);
else
status = 1;
}
else
{
PrintFormat("Failed to send an order. [%d]", GetLastError());
}
}
// 下に抜けた
else if ((rt[0].open >= ma[0]) && (rt[0].close < ma[0]))
{
// 注文
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = Lots;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
req.sl = req.price + StopPoints * _Point;
req.tp = req.price - LimitPoints * _Point;
req.deviation = AllowableSlipPoints;
req.type = ORDER_TYPE_SELL;
req.type_filling = fillMode;
req.magic = magic;
if (OrderSend(req, res))
{
Print(res.comment);
if (res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Failed to send an order. [%d] : %s", GetLastError(), res.comment);
else
status = 2;
}
else
{
PrintFormat("Failed to send an order. [%d]", GetLastError());
}
}
【 注文条件の判定 】
前述の rates_array の open(始値)、close(終値) と buffer の値から、注文条件を満たしているか確認しています。
複雑な判定はしていないので、説明は省きます。
なお、この例では窓を開けてMAを跨いだ場合はエントリーしません。
気になる人は、ローソク足の取得範囲と条件文を直してみてください。
【 成行の新規注文 】
新規注文は OrderSend関数で取引サーバに送信します。
OrderSend関数の定義は以下のとおり。
bool OrderSend(
MqlTradeRequest& request, // 取引サーバに送るリクエストの内容
MqlTradeResult& result // リクエストの結果
);
関数が正常に実行されれば true, エラーが発生すれば false が返される。
MqlTradeRequest構造体、MqlTradeResult構想体の定義は以下のとおり。
struct MqlTradeRequest
{
ENUM_TRADE_REQUEST_ACTIONS action; // 取引の種類
ulong magic; // エキスパートアドバイザー ID(マジックナンバー)
ulong order; // 注文チケット
string symbol; // 取引シンボル
double volume; // 約定のための要求されたボリューム(ロット単位)
double price; // 価格
double stoplimit; // 注文のストップリミットレベル
double sl; // 注文の決済逆指値レベル
double tp; // 注文の決済指値レベル
ulong deviation; // リクエストされた価格からの可能な最大偏差
ENUM_ORDER_TYPE type; // 注文の種類
ENUM_ORDER_TYPE_FILLING type_filling; // 注文実行の種類
ENUM_ORDER_TYPE_TIME type_time; // 注文期限切れの種類
datetime expiration; // 注文期限切れの時刻 (ORDER_TIME_SPECIFIED 型の注文)
string comment; // 注文コメント
ulong position; // Position ticket
ulong position_by; // The ticket of an opposite position
};
struct MqlTradeResult
{
uint retcode; // 操作のリターンコード
ulong deal; // 実行された場合の 約定チケット
ulong order; // 注文された場合のチケット
double volume; // ブローカーによって確認された約定ボリューム
double price; // ブローカーによって確認された約定価格
double bid; // 現在の売値
double ask; // 現在の買値
string comment; // 操作に対するブローカーコメント(デフォルトは取引サーバの返したコードの記述)
uint request_id; // ディスパッチの際に、端末によって設定されたリクエストID
int retcode_external; // 外部取引システムのリターンコード
};
MqlTradeRequest構造体を、注文の種類に応じた内容に設定しないといけないのですが、これがちょっと分かりにくいです。
この例での設定内容は以下のとおり。
メンバー | 値 |
action | TRADE_ACTION_DEAL (成行) |
magic | EAのID この例では、定数の magic。 指定しなくても動く。 複数のEAで同じ銘柄の取引を行っているときは、magicを指定しないと決済で困りそう。 |
order | この例では使用しない。 未決注文を変更する際に使用する。 |
symbol | 銘柄名 この例では、_Symbol変数。 |
volume | ロット数 この例では、input変数の Lots。 |
price | 現在の ASK/BID ※1 |
stoplimit | この例では使用しない。 指値・逆指値注文で使用する。 |
sl | Stop loss (価格) この例では、上記 price と、input変数の StopPoints から算出。 |
tp | Take profit (価格) この例では、上記 price と、input変数の LimitPoints から算出。 |
deviation | 許容スリッページ幅 (ポイント) この例では、input変数の AllowableSlipPoints。 |
type | ロング:ORDER_TYPE_BY / ショート:ORDER_TYPE_SELL |
type_filling | 注文の執行条件 成行注文では ORDER_FILLING_FOK か ORDER_FILLING_IOC を指定。 ※2 |
type_time | 指値・逆指値注文の期限の種類 この例では使用しない。 |
expiration | 指値・逆指値注文の期限の日時 この例では使用しない。 |
comment | 使用しない。 |
position | 新規注文では使用しない。 |
position_by | 新規注文では使用しない。 |
※1
現在の ASK/BID は SymbolInfoDouble関数で取得します。
SymbolInfoDouble(_Symbol, SYMBOL_ASK) で ASK、
SymbolInfoDouble(_Symbol, SYMBOL_BID) で BID を取得できます。
※2
成行注文の場合、以下から選択します。
- ORDER_FILLING_FOK
注文したロット数で約定できない場合は、注文がキャンセルされる。 - ORDER_FILLING_IOC
注文したロット数で約定できない場合は、可能な分だけ約定される。
執行条件は、取引サーバと銘柄によって指定できるものが制限されます。
指定できる執行条件は、SymbolInfoInteger関数で取得できます。
SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE) を呼ぶと、使用できる執行条件が返されます。
この処理の戻り値は、次のようなビットフラグになっています。
マスク | 使用できる執行条件 |
SYMBOL_FILLING_FOK (=1) | ORDER_FILLING_FOK |
SYMBOL_FILLING_IOC (=2) | ORDER_FILLING_IOC |
この例では、OnInit() で呼んでいる SelectFillType関数にて、執行条件を選択しています。
<実装例>
long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if ((fm & SYMBOL_FILLING_FOK) != 0)
fillMode = ORDER_FILLING_FOK;
else
fillMode = ORDER_FILLING_IOC;
OrderSend関数の戻り値が true でも、約定していない場合があります。
約定したかどうかは、MqlTradeResult構造体の retcode で判断します。
この例では、retcode が TRADE_RETCODE_DONE (リクエスト完了) かどうかを確認しています。
1ロット何通貨かは、FX会社や通貨ペアによって異なります。
EAを使ってリアルトレードをする場合は、十分にロット数にご注意ください。
エグジット
<実装例>
void CheckExit()
{
if (status == 0)
return;
// ローソク足を取得
MqlRates rt[2];
if (CopyRates(_Symbol, _Period, 0, 2, rt) != 2)
{
PrintFormat("Failed to get history data. [%d]", GetLastError());
return;
}
// MAを取得
double ma[2];
if (CopyBuffer(hMA, 0, 0, 2, ma) != 2)
{
PrintFormat("Failed to get MA values. [%d]", GetLastError());
return;
}
// ロングポジション保有中で下抜けた または
// ショートポジション保有中で上抜けた
if (((status == 1) && (rt[0].close < ma[0])) ||
((status == 2) && (rt[0].close > ma[0])))
{
// 決済
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.position = PositionGetTicket(0);
req.symbol = _Symbol;
req.volume = Lots;
req.deviation = AllowableSlipPoints;
req.type = status == 1 ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.type_filling = ORDER_FILLING_IOC;
req.magic = magic;
if (OrderSend(req, res))
{
Print(res.comment);
if (res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Failed to send an order. [%d] : %s", GetLastError(), res.comment);
}
else
{
PrintFormat("Failed to send an order. [%d]", GetLastError());
}
}
}
ローソク足取得, 移動平均値取得 ~ 成行の決済注文
ローソク足と移動平均値の取得処理は、エントリー時と一緒です。
決済条件の判定も難しいことはしていないので、説明は省きます。
決済注文も、OrderSend関数で取引サーバに送信します。
成行の決済注文における、MqlTradeRequest構造体の設定内容は以下のとおり。
メンバー | 値 |
action | TRADE_ACTION_DEAL (成行) |
magic | この例では使用しない。 |
order | 使用しない。 |
symbol | _Symbol変数。 |
volume | この例では、input変数の Lots。 執行条件が ORDER_FILLING_IOC であれば、エントリー時に MqlTradeResult構造体で受け取った約定ロット数(volume) を設定すべきだと思われる。 |
price | 使用しない。 |
stoplimit | 使用しない。 |
sl | 使用しない。 |
tp | 使用しない。 |
deviation | この例では、input変数の AllowableSlipPoints。 |
type | エントリー時と逆。 ロング:ORDER_TYPE_SELL / ショート:ORDER_TYPE_BUY |
type_filling | エントリー時と同じ。 |
type_time | 使用しない。 |
expiration | 使用しない。 |
comment | 使用しない。 |
position | 決済するポジションのチケット この例ではポジションを1つしか持たないため、PositionGetTicket関数にて、先頭のポジションのチケットを取得している。 エントリー時に MqlTradeResult構造体で受け取った約定チケット(deal) を設定してもよい。 |
position_by | 使用しない。 |
この例では OrderSend関数が false を返したとき、および、MqlTradeResult構造体の retcode が TRADE_RETCODE_DONE (リクエスト完了) 以外であった場合に何もしていませんが、リトライするなど、何かしらのエラー処理を行ったほうが安全です。
実行してみる
コンパイル
メニューの [ ビルド | コンパイル ] を選択すると、コンパイルされます。

画面下部のエラータブにエラーが表示されなければ、コンパイル完了です。
エラーが発生した場合は、例のごとく頑張ってメッセージを解釈し (あるいはググり)、コードを修正してください。
エラーメッセージをダブルクリックすると、該当する行付近にジャンプしてくれます。
<エラーなし>

<エラーあり>

実行
ここではバックテストの仕方を示します。

メニューの [ 表示 | ストラテジーテスター ] を選択する。

画面下部、ストラテジーテスターの概要タブにて、単一を選択する。

設定タブのエキスパートにて、実行するEAを選択する。

左図は、Sample1.ex5 を選択する例。

設定タブの日付にて、バックテストの実施期間を選択する。
左図は、2024/7/1~2024/7/31 を選択した例。

設定タブの入金にて、証拠金を設定する。
少なすぎるとエントリーできないので、多めに設定する。

パラメータタブでは、input変数の値を変更できる。
今回はそのままにしておく。

画面右下のスタートボタンをクリックすると、バックテストが始まる。
結果
テスト結果は、バックテストタブに表示されます。
今回は奇跡的にプラスになっています。 こんな適当なルールなのに...(;゚Д゚)

なお、期間を 2024/1/1~2024/7/31 としてみたところ、ちゃんと大きくマイナスになりました。(´∀`*)
グラフタブには、証拠金の推移が表示されます。

操作ログには、取引内容や Print関数などの出力内容が表示されます。

チャートエリアには、取引タイミングが矢印で描画されたチャートが表示されます。

エントリーしてほしくないところでエントリーしていないかや、不可解な取引をしていないかなどが一目でわかります。
例えば上のチャートだと、この辺りがめっちゃ気になりますよね。

こういう時は、関連していそうな値を Print関数などで出力してみると、原因が解ることがあります。
例えば、3か所の OrderSend関数の前に次のようなコードを追加して実行します。
( ****** は、操作ログでこのメッセージを見つけ易くするためのものです。)
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
PrintFormat("****** ask=%f, bid=%f, spread=%f", ask, bid, rt[0].spread);
次に、チャート上で、問題の矢印にカーソルを合わせます。
矢印にカーソルを合わせると、取引番号と概要が表示されます。

今回は 39番なので、操作ログにて 39番の取引近辺のログを確認します。

案の定スプレッドが広がっていましたね。
こんな感じで、実行結果を見ながら、取引ルールを調整していくことになります。
コード全体
//+------------------------------------------------------------------+
//| Sample1.mq5 |
//| Copyright 2024, Hailu |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Hailu"
#property link "https://www.mql5.com"
#property version "1.00"
input uint MaPeriod = 20;
input uint Lots = 1;
input uint LimitPoints = 1000;
input uint StopPoints = 300;
input ulong AllowableSlipPoints = 20;
int hMA = 0; // MAインジケータのハンドル
int status = 0; // 0:未エントリー / 1:ロングポジション保有 / 2:ショートポジション保有
datetime prevBarDt; // 1つ前のローソク足の開始時刻
ENUM_ORDER_TYPE_FILLING fillMode; // 執行条件
const ulong magic = 1; // このEAのID
//--------------------------------------------------------------------
// 買い・売り条件を満たしていれば注文する
//--------------------------------------------------------------------
void CheckEnter()
{
// ローソク足を取得
MqlRates rt[2];
if (CopyRates(_Symbol, _Period, 0, 2, rt) != 2)
{
PrintFormat("Failed to get history data. [%d]", GetLastError());
return;
}
// MAを取得
double ma[2];
if (CopyBuffer(hMA, 0, 0, 2, ma) != 2)
{
PrintFormat("Failed to get MA values. [%d]", GetLastError());
return;
}
// 上に抜けた
if ((rt[0].open <= ma[0]) && (rt[0].close > ma[0]))
{
// 注文
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = Lots;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.sl = req.price - StopPoints * _Point;
req.tp = req.price + LimitPoints * _Point;
req.deviation = AllowableSlipPoints;
req.type = ORDER_TYPE_BUY;
req.type_filling = fillMode;
req.magic = magic;
if (OrderSend(req, res))
{
Print(res.comment);
if (res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Failed to send an order. [%d] : %s", GetLastError(), res.comment);
else
status = 1;
}
else
{
PrintFormat("Failed to send an order. [%d]", GetLastError());
}
}
// 下に抜けた
else if ((rt[0].open >= ma[0]) && (rt[0].close < ma[0]))
{
// 注文
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.volume = Lots;
req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
req.sl = req.price + StopPoints * _Point;
req.tp = req.price - LimitPoints * _Point;
req.deviation = AllowableSlipPoints;
req.type = ORDER_TYPE_SELL;
req.type_filling = fillMode;
req.magic = magic;
if (OrderSend(req, res))
{
Print(res.comment);
if (res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Failed to send an order. [%d] : %s", GetLastError(), res.comment);
else
status = 2;
}
else
{
PrintFormat("Failed to send an order. [%d]", GetLastError());
}
}
}
//--------------------------------------------------------------------
// 決済条件を満たしていれば決済する
//--------------------------------------------------------------------
void CheckExit()
{
if (status == 0)
return;
// ローソク足を取得
MqlRates rt[2];
if (CopyRates(_Symbol, _Period, 0, 2, rt) != 2)
{
PrintFormat("Failed to get history data. [%d]", GetLastError());
return;
}
// MAを取得
double ma[2];
if (CopyBuffer(hMA, 0, 0, 2, ma) != 2)
{
PrintFormat("Failed to get MA values. [%d]", GetLastError());
return;
}
// ロングポジション保有中で下抜けた または
// ショートポジション保有中で上抜けた
if (((status == 1) && (rt[0].close < ma[0])) ||
((status == 2) && (rt[0].close > ma[0])))
{
// 決済
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.position = PositionGetTicket(0);
req.symbol = _Symbol;
req.volume = Lots;
req.deviation = AllowableSlipPoints;
req.type = status == 1 ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.type_filling = fillMode;
req.magic = magic;
if (OrderSend(req, res))
{
Print(res.comment);
if (res.retcode != TRADE_RETCODE_DONE)
PrintFormat("Failed to send an order. [%d] : %s", GetLastError(), res.comment);
}
else
{
PrintFormat("Failed to send an order. [%d]", GetLastError());
}
}
}
//--------------------------------------------------------------------
// 執行条件を選択
//--------------------------------------------------------------------
void SelectFillType()
{
long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if ((fm & SYMBOL_FILLING_FOK) != 0)
fillMode = ORDER_FILLING_FOK;
else
fillMode = ORDER_FILLING_IOC;
PrintFormat("Filling mode = %d", fillMode);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 執行条件を選択
SelectFillType();
// MAインジケータを作成
hMA = iMA(_Symbol, _Period, MaPeriod, 0, MODE_SMA, PRICE_CLOSE);
if (hMA == INVALID_HANDLE)
{
PrintFormat("Failed to create MA indicators. [%d]", GetLastError());
return INIT_FAILED;
}
// 1つ前のローソク足の時刻を初期化
prevBarDt = iTime(_Symbol, _Period, 0);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 現在のローソク足の開始時刻を取得
datetime dt = iTime(_Symbol, _Period, 0);
if (dt == 0)
{
PrintFormat("Failed to get time. [%d]", GetLastError());
return;
}
// 足確定
if (dt != prevBarDt)
{
if (status > 0)
CheckExit();
else
CheckEnter();
prevBarDt = dt;
}
}
//+------------------------------------------------------------------+
//| Trade function |
//+------------------------------------------------------------------+
void OnTrade()
{
if (PositionsTotal() <= 0)
status = 0;
}
//+------------------------------------------------------------------+
コメント