ネットで見つけたナンピン系EAのサンプルコードには、
通貨ペアを絞り込まずに他の注文まで自分のポジションとして数えてしまう、
ナンピン回数の上限が実質機能していない、損切りが一切ない、
といった欠陥が潜んでいることがあります。
ナンピン・マーチンゲールは連敗するとロットが指数関数的に増える手法なので、
これらの欠陥は「ちょっと惜しい」では済まず、
証拠金を一気に失うリスクに直結します。
この記事では、移動平均線のゴールデンクロス・デッドクロスで初回エントリーし、
逆行するたびにロットを倍にしてナンピンするEAを例に、
パラメーターの設定から実際に機能するOnTick()の中身、
そして見落としがちな「ポジション管理の絞り込み」と「損切り設定」まで、
実際にMetaEditorでコンパイルしてMT4で動かせる状態のコードを解説します。
ナンピンマーチンEA
ナンピンマーチンEAというのは、ナンピンとはポジションを増やしていく方法です。
漢字で書くと「難平」です。
これは、難を平らにならすという意味で、
転じて愚か者との意味合いもあります。
マーチンゲールというのはカジノでよく使われる手法で、
負けたら倍々に掛け金を増やしていく手法です。
ナンピンとマーチンゲールを組み合わせたナンピンマーチンというEAの種類があります。
今回はEAを作成したいと思います。
- 移動平均線のゴールデンクロスとデッドクロス
- ナンピンマーチンでポジションを取る
メタエディタ(MetaEditor)を立ち上げる
メタエディタ(MetaEditor)を立ち上げましょう。
今回は名前を「Nampin-Martingale-EA」で作成します。
MT4を起動し、ツールバーの黄色いボタン
(またはツール→MetaQuotes Language Editor)から
メタエディタを立ち上げ、
「ファイル」→「新規作成」を選び、
ウィザードで「Expert Advisor(テンプレートを使用)」を選択して
「次へ」を押します。
名前を入力する画面で「Nampin-Martingale-EA」と入力し、
以降はイベントハンドラのチェックを何も入れず
「次へ」を数回押して「完了」まで進めてください。
パラメーターを記述する
パラメーターを記述しましょう。
今回は初回ロット・スリッページ・短期/長期移動平均線の期間・
ナンピン幅・最大ナンピン数・損切り・全決済する含み益・
マジックナンバーをinput変数として用意します。
input double Lots = 0.01;//初回ロット input int Slippage = 3;//スリッページ input int Fast_MA = 5;//短期移動平均線 input int Slow_MA = 15;//長期移動平均線 input double NampinWidth = 5;//ナンピン幅(pips) input int MaxNampin = 5;//最大ナンピン数 input int StopLoss = 100;//損切り(pips、証拠金保護用) input double CloseProfit = 10;//全決済する含み益(口座通貨) input int MagicNumber = 12345;//マジックナンバー
損切り(StopLoss)と全決済する含み益(CloseProfit)は、
後述する理由でこの記事から新しく追加・整理したパラメーターです。
元のコードのslippageはdouble型で宣言されていましたが、
OrderSend・OrderCloseのスリッページ引数は本来int型のため、
int型に修正しています。
OnTick()関数を記述する
OnTick()関数の中身を説明します。
まず、このEA(マジックナンバー)が保有しているポジションの
含み損益の合計を計算します。
double totalProfit=0;
for(int i=0;i<OrdersTotal();i++)
{
if(!OrderSelect(i,SELECT_BY_POS)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
totalProfit+=OrderProfit()+OrderSwap()+OrderCommission();
}
ポジションを持っていない場合は、
移動平均線のゴールデンクロス・デッドクロスで新規エントリーします。
double kako_Fast,kako_Slow;//過去のMA
double gen_Fast,gen_Slow;//現在のMA
kako_Fast=iMA(NULL,0,Fast_MA,0,MODE_SMA,PRICE_CLOSE,1);//過去の短期MA
kako_Slow=iMA(NULL,0,Slow_MA,0,MODE_SMA,PRICE_CLOSE,1);//過去の長期MA
gen_Fast =iMA(NULL,0,Fast_MA,0,MODE_SMA,PRICE_CLOSE,0);//現在の短期MA
gen_Slow =iMA(NULL,0,Slow_MA,0,MODE_SMA,PRICE_CLOSE,0);//現在の長期MA
int ticket;
//ゴールデンクロス時
if(kako_Fast<kako_Slow && gen_Fast>=gen_Slow)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,0,"buy",MagicNumber,0,Red);
if(ticket<0) Print("買い注文に失敗しました。エラーコード=",GetLastError());
}
//デッドクロス時
if(kako_Fast>kako_Slow && gen_Fast<=gen_Slow)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,Slippage,Bid+StopLoss*Point*10,0,"sell",MagicNumber,0,Blue);
if(ticket<0) Print("売り注文に失敗しました。エラーコード=",GetLastError());
}
ポジションを持っている場合は、
含み益の合計が一定額(CloseProfit)を超えていればすべて決済し、
そうでなければ逆行幅がナンピン幅(NampinWidth)を超えたところで、
ロットを2倍にしたナンピンを追加します。
//含み益の合計がCloseProfitを超えたら全決済
if(totalProfit>CloseProfit)
{
allClose(clrGreen);
return;
}
//最大ナンピン数に達していたら追加エントリーしない
if(CountMyPositions()>=MaxNampin+1) return;
int ticket;
if(OrderType()==OP_BUY)
{
if(Close[0]<(OrderOpenPrice()-NampinWidth*Point*10))
{
ticket=OrderSend(Symbol(),OP_BUY,OrderLots()*2,Ask,Slippage,Ask-StopLoss*Point*10,0,"buy2",MagicNumber,0,Blue);
if(ticket<0) Print("ナンピン買い注文に失敗しました。エラーコード=",GetLastError());
}
}
if(OrderType()==OP_SELL)
{
if(Close[0]>(OrderOpenPrice()+NampinWidth*Point*10))
{
ticket=OrderSend(Symbol(),OP_SELL,OrderLots()*2,Bid,Slippage,Bid+StopLoss*Point*10,0,"sell2",MagicNumber,0,Red);
if(ticket<0) Print("ナンピン売り注文に失敗しました。エラーコード=",GetLastError());
}
}
ポジション管理を通貨ペア・マジックナンバーで正確に絞り込む
元のコードは、ポジションの有無やナンピン回数の判定に、
口座全体の注文数を返すOrdersTotal()をそのまま使っていました。
この方法だと、同じ口座で他のEAや裁量トレードのポジションを持っている場合、
それらまで自分のポジションとして数えてしまい、
最大ナンピン数(MaxNampin)の上限判定が狂います。
これを防ぐため、通貨ペア(Symbol())とマジックナンバーが
一致する注文だけを数える関数と、
最後の該当ポジションを選択する関数を用意します
(この2つの関数は、前のセクションのOnTick()の中で先に呼び出しています)。
//このシンボル・マジックナンバーのポジション数を数える
int CountMyPositions()
{
int count=0;
for(int i=0;i<OrdersTotal();i++)
{
if(!OrderSelect(i,SELECT_BY_POS)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
count++;
}
return(count);
}
//このシンボル・マジックナンバーの最後のポジションを選択する
bool SelectLastMyPosition()
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderSelect(i,SELECT_BY_POS)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
return(true);
}
return(false);
}
損切り(ストップロス)を設定する
元のコードのOrderSend()は、損切り価格(第6引数)が常に0になっており、
損切りが一切設定されていませんでした。
ナンピンマーチンは、逆行するたびにロットを倍にしていく手法のため、
相場が戻らないまま逆行し続けると、証拠金があっという間に足りなくなります。
学習用のシンプルな例であっても、口座を守るための強制損切りラインは必須です。
パラメーターに追加したStopLossを使って、
それぞれの注文のエントリー価格から一定距離に損切りを設定します。
//買いの場合 Ask-StopLoss*Point*10 //売りの場合 Bid+StopLoss*Point*10
※Pointは通貨ペアの最小価格単位です。5桁表示のブローカーでは1pip=10Pointになるため、pips単位で指定した値に10を掛けています。4桁表示のブローカーを使う場合は「×10」を外してください(詳しくはゴールデンクロス・デッドクロスEAの記事を参照)。
allCloseの記述
allClose()は、このEA(マジックナンバー)が
保有しているすべてのポジションを決済する関数です。
含み益の合計が一定額を超えたときに呼び出します。
//すべて決済する関数
void allClose(color clr)
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
if(OrderSymbol()!=Symbol()||OrderMagicNumber()!=MagicNumber) continue;
if(OrderType()==OP_BUY)
OrderClose(OrderTicket(),OrderLots(),Bid,Slippage,clr);
if(OrderType()==OP_SELL)
OrderClose(OrderTicket(),OrderLots(),Ask,Slippage,clr);
}
}
決済ループはOrdersTotal()-1から0まで逆順に回しています。
決済すると注文の総数(インデックス)がその場でずれるため、
0から昇順にループすると一部の注文を数え飛ばしてしまいます。
逆順ループはこのズレを避けるための定石です。
コンパイル
コードを保存したら、メニューの「コンパイル」(Ctrl+F7)で
「0 error(s), 0 warning(s)」になることを確認します。
ソースコード全体
ここまでの修正をすべて反映した、
実際にMetaEditorでコンパイルしてそのまま動かせる状態のコードです。
//+------------------------------------------------------------------+
//| Nampin-Martingale-EA.mq4 |
//| Copyright 2020, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.01"
#property strict
input double Lots = 0.01;//初回ロット
input int Slippage = 3;//スリッページ
input int Fast_MA = 5;//短期移動平均線
input int Slow_MA = 15;//長期移動平均線
input double NampinWidth = 5;//ナンピン幅(pips)
input int MaxNampin = 5;//最大ナンピン数
input int StopLoss = 100;//損切り(pips、証拠金保護用)
input double CloseProfit = 10;//全決済する含み益(口座通貨)
input int MagicNumber = 12345;//マジックナンバー
//+------------------------------------------------------------------+
//| このシンボル・マジックナンバーのポジション数を数える |
//+------------------------------------------------------------------+
int CountMyPositions()
{
int count=0;
for(int i=0;i<OrdersTotal();i++)
{
if(!OrderSelect(i,SELECT_BY_POS)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
count++;
}
return(count);
}
//+------------------------------------------------------------------+
//| このシンボル・マジックナンバーの最後のポジションを選択する |
//+------------------------------------------------------------------+
bool SelectLastMyPosition()
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderSelect(i,SELECT_BY_POS)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
double totalProfit=0;
for(int i=0;i<OrdersTotal();i++)
{
if(!OrderSelect(i,SELECT_BY_POS)) continue;
if(OrderSymbol()!=Symbol()) continue;
if(OrderMagicNumber()!=MagicNumber) continue;
totalProfit+=OrderProfit()+OrderSwap()+OrderCommission();
}
bool hasPosition=SelectLastMyPosition();
if(!hasPosition)//ポジションを持っていない場合
{
double kako_Fast,kako_Slow;//過去のMA
double gen_Fast,gen_Slow;//現在のMA
kako_Fast=iMA(NULL,0,Fast_MA,0,MODE_SMA,PRICE_CLOSE,1);//過去の短期MA
kako_Slow=iMA(NULL,0,Slow_MA,0,MODE_SMA,PRICE_CLOSE,1);//過去の長期MA
gen_Fast =iMA(NULL,0,Fast_MA,0,MODE_SMA,PRICE_CLOSE,0);//現在の短期MA
gen_Slow =iMA(NULL,0,Slow_MA,0,MODE_SMA,PRICE_CLOSE,0);//現在の長期MA
int ticket;
//ゴールデンクロス時
if(kako_Fast<kako_Slow && gen_Fast>=gen_Slow)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,0,"buy",MagicNumber,0,Red);
if(ticket<0) Print("買い注文に失敗しました。エラーコード=",GetLastError());
}
//デッドクロス時
if(kako_Fast>kako_Slow && gen_Fast<=gen_Slow)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,Slippage,Bid+StopLoss*Point*10,0,"sell",MagicNumber,0,Blue);
if(ticket<0) Print("売り注文に失敗しました。エラーコード=",GetLastError());
}
}
else//既にポジションを持っている場合
{
//含み益の合計がCloseProfitを超えたら全決済
if(totalProfit>CloseProfit)
{
allClose(clrGreen);
return;
}
//最大ナンピン数に達していたら追加エントリーしない
if(CountMyPositions()>=MaxNampin+1) return;
int ticket;
if(OrderType()==OP_BUY)
{
if(Close[0]<(OrderOpenPrice()-NampinWidth*Point*10))
{
ticket=OrderSend(Symbol(),OP_BUY,OrderLots()*2,Ask,Slippage,Ask-StopLoss*Point*10,0,"buy2",MagicNumber,0,Blue);
if(ticket<0) Print("ナンピン買い注文に失敗しました。エラーコード=",GetLastError());
}
}
if(OrderType()==OP_SELL)
{
if(Close[0]>(OrderOpenPrice()+NampinWidth*Point*10))
{
ticket=OrderSend(Symbol(),OP_SELL,OrderLots()*2,Bid,Slippage,Bid+StopLoss*Point*10,0,"sell2",MagicNumber,0,Red);
if(ticket<0) Print("ナンピン売り注文に失敗しました。エラーコード=",GetLastError());
}
}
}
}
//+------------------------------------------------------------------+
//| すべて決済する関数 |
//+------------------------------------------------------------------+
void allClose(color clr)
{
for(int i=OrdersTotal()-1;i>=0;i--)
{
if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
if(OrderSymbol()!=Symbol()||OrderMagicNumber()!=MagicNumber) continue;
if(OrderType()==OP_BUY)
OrderClose(OrderTicket(),OrderLots(),Bid,Slippage,clr);
if(OrderType()==OP_SELL)
OrderClose(OrderTicket(),OrderLots(),Ask,Slippage,clr);
}
}
//+------------------------------------------------------------------+
バックテストで動作を確認する
コードを保存したら、MetaEditorの「コンパイル」(Ctrl+F7)で
「0 error(s), 0 warning(s)」になることを確認します。
そのあとMT4の「表示」→「ストラテジーテスター」を開き、
Expert AdviserにNampin-Martingale-EAを選択、
通貨ペアと期間を指定して、
モデルを「全ティック(最も正確な値)」にしてスタートすると、
過去データでの動作を確認できます。
特に、ナンピンが最大回数(MaxNampin)に達したところで
新規のナンピンが止まっているか、証拠金がどこまで減るかを確認してください。
実運用する前に確認しておきたいこと
ナンピンマーチンは、連敗が続くとロットが指数関数的に増える手法です。
理論上は「いつか反転すれば利益が出る」という考え方ですが、
実際の相場では逆行が長く続くこともあり、
証拠金がロスカット水準に達すればポジションは強制決済されます。
- MaxNampin(最大ナンピン数)を大きくするほど、必要な証拠金と最大損失額も指数関数的に増える
- StopLoss(損切り)を入れても、ナンピンによってロットが増えているため、損切りに達したときの損失額は初回ポジションより大きくなる
- バックテストで良い成績が出ても、テスト期間にたまたま大きな逆行相場が含まれていなかっただけの可能性がある
ロットは1回逆行するごとに倍になるため、
初回0.01ロットでMaxNampin=5(初回+ナンピン5回)まで進むと、
合計ロットは0.01+0.02+0.04+0.08+0.16+0.32=0.63ロットまで
膨らみます。
この合計ロットに必要な証拠金がいくらになるかを、
自分の口座の通貨ペア・レバレッジで事前に計算しておいてください
(必要証拠金の目安は「ロット数×取引単位×為替レート÷レバレッジ」で、
MT4の「気配値表示」右クリック→「詳細」や
ブローカーの証拠金計算ツールでも確認できます)。
まずはデモ口座、または証拠金に対して十分小さいロットでフォワードテストを行い、
最大何回のナンピンまで自分の資金が耐えられるかを確認してから、
本番運用を検討してください。
【MQL】ナンピンマーチンEA【上級編】:まとめ
移動平均線のゴールデンクロス・デッドクロスで初回エントリーし、
逆行するたびにロットを倍にしてナンピンするEAの作り方を解説しました。
ナンピンマーチンは資金管理を誤ると損失が急拡大する手法だからこそ、
ポジション管理の絞り込みと損切り設定を必ず組み込むことが欠かせません。
コメント