この記事の内容をコピペしていけば、最もシンプルなナンピンマーチン式のFX自動売買ツール(ここではナンピンマーチンEAと呼びます)をMQL4ではなくMQL5で作成することが可能です。
ナンピンマーチンEAのロジック
基本的なナンピンマーチンEAの設計概要は以下のように非常にシンプルです。
- Inputの設定
- ループ処理
- ポジションの確認
- 新規エントリー注文
- 追加エントリー(ナンピン)注文
- ポジションクローズ注文
ロジック概要
複雑な条件は可能な限り省いて簡略化しています。
初期lot | 0.01lot |
エントリー | 稼働時間内であればすぐに新規ポジションを取る |
ナンピン幅 | 最低価格変動0.01に対して、価格幅2.00 |
マーチン倍率 | マーチンゲール法(倍率1.5倍) |
利確目標 | 1ポジション当たり143円 |
稼働時間 | 取引時間帯はフル稼働 |
強制クローズ | なし(翌日にポジションを持ち越す) |
ここで作成するEAのロジックは、以下のMT4およびPythonで作成したものと同じロジックです。必要に応じて、コードの違いを比較してみてください。
Inputの設定
主にEAのロジックを決定する部分です。またMT5で動かす時に外部からインプットして設定出来る部分でもあります。
input double first_lot = 0.01;//初期ロット
input double nanpin_range = 200;//ナンピン幅
input double profit_target = 143;//利益目標
input int magic_number = 10001; //マジックナンバー
double slippage = 10;//スリッページ
初期ロット(first_lot)
1番目のポジションを取得する時のロット数の設定です。最小は0.01ですので、ひとまず0.01に設定しておきます。
ナンピン幅(nanpin_range)
1番目のポジションの後、どれくらい価格が動いたら次のポジションを取りに行くかという価格幅の設定です。200と設定すると、GOLDの場合、2.00の価格幅を意味します。(厳密には、この後に *Pointという形で単位を決めにいきます。)
利益目標(profit_target)
利益目標を金額で設定します。ここでは単位は円で、143円という設定です。1ポジションあたり143円の含み益が発生したら、クローズ注文を行うようなロジックです。
マジックナンバー(magic_number)
どんなEAにも存在する識別番号のようなものです。他のEAと同時に動かすようなことがなければ、どんな数値でも構いません。ロジックには関係ありません。ここでは適当に10001と設定しています。
スリッページ(slippage)
MT5では、注文時にスリッページの許容範囲が設定可能です。ただし、業者によっては設定しても無効になるようです。XMTradingもその一つで、注文時にスリッページの許容範囲を設定しても無効になります。ただし、以下の公式の説明の通り、スリッページはほとんど発生しません。
スリッページは発生しますか?
当社で取引される場合、スリッページはほとんど発生しません。しかし、特に重要な経済ニュースの発表時などは、市場価格の急騰または急落により、お客様の注文はリクエストされたものと異なるレートで執行される場合があります。
ここでは適当に10と設定しておきます。
ループ処理
初期設定が終わり、ここからループ処理に入ります。
void OnTick()
void OnTick()
{
これはイベント関数と呼ばれるものの一つで、EAが新しいtickを受信した時に発生します。
もう少しわかりやすく言い換えると、価格が変動する度に繰り返し行う処理ということです。
従って、主要なロジックはほとんどこの中に記載していきます。
これ以降はしばらく、void OnTick()の中に記載していく内容になります。
変数の定義
まずは、ループ処理内で使う変数の定義です。
int buy_position = 0;//buyポジション数
int sell_position = 0;//sellポジション数
double buy_profit = 0.0;//buyポジションの含み損益
double sell_profit = 0.0;//sellポジションの含み損益
double current_buy_lot = 0.0;//最新のbuyポジションのロット数
double current_sell_lot = 0.0;//最新のsellポジションのロット数
double current_buy_price = 0.0;//最新のbuyポジションの価格
double current_sell_price = 0.0;//最新のsellポジションの価格
buy_position、sell_position
buy_positionは、buyポジションの数を意味します。buyポジション数をいくつ持っているかを判定する際に使用します。sell_positionも同様です。
buy_profit、sell_profit
buy_profitは、buyポジションの合計含み損益を意味します。buyポジション数を複数持っている場合は、複数ポジションの含み損益を通算した数値になります。クローズ注文を行うかどうか判定する際に使用します。sell_profitも同様です。
current_buy_lot、current_sell_lot
current_buy_lotは、最新のbuyポジションのロット数を意味します。ナンピンを行う際に、前のポジションの1.5倍のロット数という設定を実現するために最新のロット数を記録しておくための変数です。current_sell_lotも同様です。
current_buy_price、current_sell_price
current_buy_priceは、最新のbuyポジションの取得価格を意味します。前のポジションの取得価格から一定の幅だけ乖離した場合にナンピンを行う、という設定を実現するために最新のポジション価格を記録しておくための変数です。current_sell_priceも同様です。
ポジションの確認
ここでは、ポジションを確認する処理を行います。
具体的には、buyポジションとsellポジションをそれぞれいくつ持っているか、最新のポジションの番号(cnt)を取得しておき、次の注文の際に使用します。
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetSymbol(i)!="")
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
buy_position++;
buy_profit += PositionGetDouble(POSITION_PROFIT);
current_buy_lot = PositionGetDouble(POSITION_VOLUME);
current_buy_price = PositionGetDouble(POSITION_PRICE_OPEN);
}
else
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
sell_position++;
sell_profit += PositionGetDouble(POSITION_PROFIT);
current_sell_lot = PositionGetDouble(POSITION_VOLUME);
current_sell_price = PositionGetDouble(POSITION_PRICE_OPEN);
}
}
}
簡単に中身を解説しておくと、全てのポジションを順番に確認していき、
buyポジションだったら、
- buy_positionを1つ増やす。
- buy_profitにそのポジションのprofitを加える。
- current_buy_lotにlot数を記録(上書きを繰り返すので最後のbuyポジションの情報が最終的に残る)
- current_buy_priceに取得価格を記録(上書きを繰り返すので最後のbuyポジションの情報が最終的に残る)
sellポジションだったら、
- sell_positionを1つ増やす。
- sell_profitにそのポジションのprofitを加える。
- current_sell_lotにlot数を記録(上書きを繰り返すので最後のsellポジションの情報が最終的に残る)
- current_sell_priceに取得価格を記録(上書きを繰り返すので最後のsellポジションの情報が最終的に残る)
というような処理を行なっています。
最新価格(ティック)の取得
ここで、MT4では不要でもMT5では必要な処理を入れておきます。
MqlTick last_tick;
SymbolInfoTick(_Symbol,last_tick);
double Ask=last_tick.ask;
double Bid=last_tick.bid;
MT4の場合は、AskやBidは組み込み変数としてそのまま使えるのですが、MT5の場合は、このようにAskやBidを最新ティック(last_tick)から取得処理を入れておく必要があります。
新規エントリー注文
続いて、新規エントリー注文です。エントリー条件はシンプルに、「ポジションを持っていなかったら」という条件のみです。
if(buy_position == 0)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // 成行注文
request.type = ORDER_TYPE_BUY; // 注文タイプ
request.magic = magic_number; // マジックナンバー
request.symbol = Symbol(); // 通貨ペア名
request.volume = first_lot; // ロット数
request.price = Ask;// 注文価格
request.deviation = slippage; // スリッページ
request.comment = "first_buy"; // コメント
request.type_filling = ORDER_FILLING_IOC; // ボリューム実行ポリシー
OrderSend(request, result);
}
if(sell_position == 0)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // 成行注文
request.type = ORDER_TYPE_SELL; // 注文タイプ
request.magic = magic_number; // マジックナンバー
request.symbol = Symbol(); // 通貨ペア名
request.volume = first_lot; // ロット数
request.price = Bid;// 注文価格
request.deviation = slippage; // スリッページ
request.comment = "first_sell"; // コメント
request.type_filling = ORDER_FILLING_IOC; // ボリューム実行ポリシー
OrderSend(request, result);
}
OrderSend関数
MT5でも、OrderSend関数というのを使用するのですが、MT4のOrderSend関数とは結構異なります。MT5の場合は、「リクエスト構造体(request)」という注文情報のようなものを作成して、OrderSend関数でそのリクエストを送信する、というような処理イメージです。
成行注文
TRADE_ACTION_DEALというのは成行注文を表します。
注文タイプ
ORDER_TYPE_BUY、ORDER_TYPE_SELLというのはそれぞれbuyとsellです。注文価格のAsk、Bidというのはその時の価格です。
つまり、buyあるいはsellのそれぞれのポジションを持っていなかったら、その時の価格ですぐに成行注文を行う(成行注文でも価格を指定して注文を行います。)、そのロット数は最初の Inputで設定したfirst_lot(初期ロット)ということです。
コメント
コメントは、MT5で注文を確認する際に表示されるものです。設定しなくても問題ありませんが、今回は”first_buy”や”first_sell”というように設定しておきます。
ボリューム実行ポリシー
MT4と異なり、request.type_fillingというのを設定する必要があります。少なくともXMTradingの場合はORDER_FILLING_IOCで大丈夫かと思います。詳しくは、ENUM_ORDER_TYPE_FILLINGを参照して下さい。
追加エントリー(ナンピン)注文
そして、肝心のナンピン注文の設定です。
とは言っても特に難しいことはしておらず、新規エントリー注文との違いは、
- ナンピン幅の条件を満たした場合にのみ注文を行うこと
- ロット数を前回のロット数の1.5倍にしていること
の2つくらいです。
if(buy_position > 0)
{
if(Ask < current_buy_price - nanpin_range * Point()) //現在価格がナンピン幅に達しているか
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // 成行注文
request.type = ORDER_TYPE_BUY; // 注文タイプ
request.magic = magic_number; // マジックナンバー
request.symbol = Symbol(); // 通貨ペア名
request.volume = round(current_buy_lot*1.5*100)/100; // ロット数
request.price = Ask;// 注文価格
request.deviation = slippage; // スリッページ
request.comment = "nanpin_buy"; // コメント
request.type_filling = ORDER_FILLING_IOC; // ボリューム実行ポリシー
OrderSend(request, result);
}
}
if(sell_position > 0)
{
if(Bid > current_sell_price + nanpin_range * Point()) //現在価格がナンピン幅に達しているか
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; // 成行注文
request.type = ORDER_TYPE_SELL; // 注文タイプ
request.magic = magic_number; // マジックナンバー
request.symbol = Symbol(); // 通貨ペア名
request.volume = round(current_sell_lot*1.5*100)/100; // ロット数
request.price = Bid;// 注文価格
request.deviation = slippage; // スリッページ
request.comment = "nanpin_sell"; // コメント
request.type_filling = ORDER_FILLING_IOC; // ボリューム実行ポリシー
OrderSend(request, result);
}
}
現在価格がナンピン幅に達しているか
if(Ask < current_buy_price - nanpin_range * Point()) //現在価格がナンピン幅に達しているか
current_buy_priceは、ポジション確認で取得した最新ポジションの価格です。例えば、1740.21という価格とします。
nanpin_rangeは、Inputで設定したナンピン幅200です。Pointは、通貨ペア(取引対象)毎に定まる最小単位です。XMTradingのGOLDの場合は、0.01を意味します。
つまりこのif文は、
という条件になります。
ポジションクローズ注文
最後に、ポジションクローズ注文です。
以下のように、非常にシンプルですが、ここでは後で別途定めるbuyClose関数とsellClose関数というものを使用しています。
if(buy_position>0&&buy_profit>profit_target*buy_position)
{
buyClose();//すべてbuyポジションをクローズ
}
if(sell_position>0&&sell_profit>profit_target*sell_position)
{
sellClose();//すべてsellポジションをクローズ
}
ポジションクローズ条件
buyポジションのクローズ注文を行う条件は、以下の2つを同時に満たした場合です。
- buyポジションを1つ以上持っている
- buyポジションの含み益合計が、利益目標×buyポジション数を上回っている
これを上記算式で表現しています。具体的には、例えばbuyポジション数が3だった場合、利益目標(profit_target)は143円と設定していたので、143円の3倍である429円を、buyポジションの含み益合計が上回った場合に、buyポジションのクローズ注文を行います。
ポジションを決済する関数
以下は、「void OnTick()」の外で別途定義しておきます。オリジナルの関数を定義しておいて、先ほどのポジションクローズ注文のところで呼び出します。
buyClose関数
持っているbuyポジションを全てクローズしていく関数です。
マジックナンバーが同じもの、かつ、buyポジションだけを全てクローズします。
void buyClose()
{
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetSymbol(i)!="")
{
if(PositionGetInteger(POSITION_MAGIC)==magic_number)
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.position =PositionGetTicket(i); // ポジションチケット
request.action = TRADE_ACTION_DEAL; // 成行注文
request.type = ORDER_TYPE_SELL; // 注文タイプ
request.magic = magic_number; // マジックナンバー
request.symbol = Symbol(); // 通貨ペア名
request.volume = PositionGetDouble(POSITION_VOLUME); // ロット数
request.price = SymbolInfoDouble(_Symbol,SYMBOL_BID); // 注文価格
request.deviation = slippage; // スリッページ
request.type_filling = ORDER_FILLING_IOC; // ボリューム実行ポリシー
OrderSend(request, result);
}
}
}
}
}
sellClose関数
持っているsellポジションを全てクローズしていく関数です。
マジックナンバーが同じもの、かつ、sellポジションだけを全てクローズします。
void sellClose()
{
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetSymbol(i)!="")
{
if(PositionGetInteger(POSITION_MAGIC)==magic_number)
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.position =PositionGetTicket(i); // ポジションチケット
request.action = TRADE_ACTION_DEAL; // 成行注文
request.type = ORDER_TYPE_BUY; // 注文タイプ
request.magic = magic_number; // マジックナンバー
request.symbol = Symbol(); // 通貨ペア名
request.volume = PositionGetDouble(POSITION_VOLUME); // ロット数
request.price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); // 注文価格
request.deviation = slippage; // スリッページ
request.type_filling = ORDER_FILLING_IOC; // ボリューム実行ポリシー
OrderSend(request, result);
}
}
}
}
}
まとめ
以上、これまで見てきたものを組み合わせるだけで、基本的なナンピンマーチンEA(MT5用)は完成です。
いかがでしたでしょうか。思ったよりシンプルな構造ということがよくわかったかと思います。
.mq5ファイルのダウンロード
今回ご紹介したコードをまとめたものは以下からダウンロード可能です。
ea001.mq5
EAのバックテスト方法
以下の記事では、作成したFX自動売買ツール(bot/EA)を、Google Colabを使ってPythonでバックテストを行う方法を紹介しています。botやEAの作成に正確なバックテストは不可欠ですので、是非こちらもご覧ください。
MQL4で作るFX自動売買ツール(EA)
MQL4でFX自動売買ツール(EA)を作成する方法もご紹介しています。
Pythonで作るFX自動売買ツール(bot)
PythonでFX自動売買ツール(bot)を作成する方法もご紹介しています。
この記事で紹介したナンピンマーチンEAと全く同じロジックで作成していますので、是非見比べてみてください。
キャッシュバック口座の開設
作成したEAを利用して実際に運用する際は、通常の口座開設ではなく、キャッシュバック口座を開設するのがおすすめです。以下の記事で、キャッシュバックサイト経由で海外FX口座を開設する際のメリットとデメリットをまとめていますのでご覧ください。
リアル口座をどのように開設するのが最適か考える参考になると思います。
ChatGPTと作るFX自動売買ツール(EA)
プログラミングも出来るチャット型AI(ChatGPT)と協力して同様のEAを作成してみた記事はこちらです。