FX自動売買基礎と応用

朝スキャEAにナンピンを実装するカスタマイズ【コピペで使用可能】


前回の記事では、単純な時間制限と逆張りのロジックに対してマーチンゲールを実装することで、どのような影響が出るのかを実験しました。
今回の記事では、ナンピンを実装することでバックテストの結果がどのように変化するのかを確認していきましょう。

ナンピンとは相場がエントリーと逆の方向に動いたときに、ポジションを追加で保有して平均取得単価を下げることです。
ナンピンロジックは大変人気があり、多くの有名EAに搭載されている機能です。

ナンピンのメリットとデメリット

ナンピンのメリットとデメリットについて紹介します。

<メリット>

例えば、買いエントリーを行った時に、相場が逆行したとします。
この時ナンピンの買いエントリーを行うことで、損益分岐点を引き下げて、利確のチャンスを得やすくなります。
さらに相場が上昇すれば、エントリーしたロット数だけ利益にもなります。
ナンピンを前提とした資金管理、ロット設定を行うことで、効率的に有利なトレードを行えます。

<デメリット>

ナンピンは、必ず利益が出ることを保証する方法ではありません。
証拠金が足りなくなり、強制ロスカットが行われるリスクもあります。
そのため、ナンピンの回数に制限をかけるなど、安全な運用を行う必要があります。

仕様

まずは前回作成したEAの仕様をまとめます。

エントリー

時間が日本時間早朝かつRSIが売られすぎラインを下回ったら、買いエントリー
時間が日本時間早朝かつRSIが買われすぎラインを上回ったら、売りエントリー

決済

利確価格と損切り価格による決済

その他

著作権を「OANDA」に設定
スリッページ、スプレッドによってエントリーを制限する
ポジションは同時に1つのみ保有するようにする
マジックナンバーとロット数を変更できるようにする

以上です。
今回はナンピンを行う幅をパラメータで設定できるようにします。
エントリーされたポジションがパラメータで設定した幅だけ損失が出たときに、ナンピンを行うようにします。

EAを作成する

プログラムは「5.朝スキャのEAを作ってみよう」をもとに作成していきます。
それまでの内容は割愛し、今回追加する機能のみ紹介します。

フィールド


input int                   MAGICMA = 23198721;                      // マジックナンバー
input double                Lots =0.01;                                 // 1ロット十万通貨単位
input int                   Slippage = 4;                                // エントリー見送りスリッページ
input double                MaxSpread = 5;                              // エントリー見送りスプレッド
input double                TakeProfit = 10.0;                           // 利益確定幅(pips)
input double                LossCut = 20.0;                             // 損切り確定幅(pips)
input int                   RSIPeriod=6;                                // 期間
input   ENUM_APPLIED_PRICE  RSIAppliedPrice = PRICE_CLOSE;      // 適用価格
input int                   UpLine = 85;                                // 上の線
input int                   DownLine = 25;                              // 下の線
input int                   TradeTime = 0;                              // トレードを行う時間

//ナンピン設定
input int                   NanpinWidth = 5;                            //ナンピンの幅(pips)

前回のパラメータの最後にNanpinWidthという変数を追加します。

OnTick関数


void OnTick()
{
   dSpread = (Ask - Bid) / (Point * 10);
   
   if(CalculateCurrentOrders()==0 && dSpread < MaxSpread) CheckForOpen(); 
   if(CalculateCurrentOrders()==1 && dSpread < MaxSpread) CheckForNanpin();    //①
   CheckForClose();                                                            //②
}

OnTick関数です。

  • ①.ナンピンによる売買はCheckForOpenではなくCheckForNanpin関数から行わせるようにします。
    またCheckForNanpin関数は保有しているポジションが1つで、スプレッド制限に引っかかっていない場合のみ呼び出すようにします。
  • ②.今回は決済用の関数CheckForCloseを用意します。
    前回まではOrderSend関数の引数から指値と逆指値を設定していましたが、ナンピンの場合は保有している合計の損益に対して一括で決済することが望ましく、新たに決済用の関数を作成します。

CheckForOpen関数


void CheckForOpen()
{
   int res;
   double RSI = iRSI(Symbol(), 0, RSIPeriod, RSIAppliedPrice, 1);
   if(TradeTime == TimeHour(Time[1]))
   {
      if(RSI < DownLine)
      {
         res=OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0,"", MAGICMA, 0, Red);    //①
      }
      if(RSI > UpLine)
      {
         res=OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, "", MAGICMA, 0, Blue);    //①
      }
   }
}

①.今回は指値・逆指値は指定しないので、OrderSendの指値・逆指値設定を0とします。

CheckForNanpin関数


void CheckForNanpin()
{
   int res;
//①
   double buyProfitPips = 0;     //①
   double SellProfitPips = 0;
 //②
 for( int i=0; i<OrdersTotal(); i++ ){
      if( OrderSelect(i, SELECT_BY_POS) == true && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
         if( OrderType() == OP_BUY ){
            buyProfitPips = (MarketInfo(Symbol(),MODE_BID) - OrderOpenPrice()) / (Point * 10); 
            break;
         }else if( OrderType() == OP_SELL ){
            SellProfitPips = (OrderOpenPrice() - MarketInfo(Symbol(),MODE_ASK)) / (Point * 10);
            break;
         }  
      }
   }
 //③
   if(SellProfitPips == 0 && buyProfitPips < NanpinWidth * -1)
   {
      res=OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0,"", MAGICMA, 0, Red);
   }
   if(buyProfitPips == 0 && SellProfitPips < NanpinWidth * -1)
   {
      res=OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0,  0, "", MAGICMA, 0, Blue);
   }

}}
  • ①.保有している買い、もしくは売りのポジションの損益を格納する変数を用意します。
  • ②.保有しているポジションの内、損益を取得する部分です。
    取得する値は1つのポジションだけなので、取得後はbreakでfor文を抜けるようにします。
  • ③.損失がパラメータで設定した幅以上に広がっている場合のみ、エントリーを行うようにします。
    ポイントは、BuyProfitPipsとSellProfitPipsの値は損失が発生している場合符号がマイナスになるので、パラメータの幅と合わせるために-1をかける必要がある点です。

CheckForClose関数


void CheckForClose(){
   int res;
   double buyProfitPips = 0;
   double SellProfitPips = 0;
   //①
   for( int i=0; i<OrdersTotal(); i++ ){
      if( OrderSelect(i, SELECT_BY_POS) == true && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
         if( OrderType() == OP_BUY ){
            buyProfitPips = (MarketInfo(Symbol(),MODE_BID) - OrderOpenPrice()) / (Point * 10); 
         }else if( OrderType() == OP_SELL ){
            SellProfitPips = (OrderOpenPrice() - MarketInfo(Symbol(),MODE_ASK)) / (Point * 10);
         }  
      }
   }
//②
   if(TakeProfit <= buyProfitPips || LossCut <= buyProfitPips * -1){
      for( int i=OrdersTotal()-1; i>=0; i-- ){
         if( OrderSelect(i, SELECT_BY_POS) == true ){
            if( OrderType() == OP_BUY && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
               res = OrderClose(OrderTicket(), OrderLots(), Bid, Slippage * 10,Green);
            }
         }
      }
   }
//②
   if(TakeProfit <= SellProfitPips || LossCut <= SellProfitPips * -1){
      for( int i=OrdersTotal()-1; i>=0; i-- ){
         if( OrderSelect(i, SELECT_BY_POS) == true ){
            if( OrderType() == OP_SELL && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
               res = OrderClose(OrderTicket(),OrderLots(),Ask,Slippage * 10,Green);
            }
         }
      }
   }
}
  • 1.現在EAが保有している全ポジションの損益を計算します。
    今回は損益の合計を求めたいので、break文でfor文を抜けるといったことはしません。
  • 2.計算した損益がパラメータの利確、損切り幅よりも大きいときに、保有しているポジションを全決済します。

バックテスト

今回作成したマーチンゲールのEAと、前回作成した単ポジ型のEAのバックテスト結果を比較してみましょう。
条件は以下の通りです。

  • 通貨ペア:GBPUSD
  • 時間足:5分足
  • 期間:2018年1月1日~2023年1月1日の5年間
  • スプレッド:15固定

単ポジ型

バックテスト(単ポジ型)

ナンピンのEA

バックテスト(ナンピンのEA)

まずプロフィットファクターですが、1.21から1.67と大幅に伸びていることが分かります。

ナンピンによって損益分岐点が引き下げられた分、利確のチャンスが増えたのでプロフィットファクターの値も大きくなっています。
取引回数についてもナンピンを行う分、単純に2倍前後増えています。
最大ドローダウンを見てみると、0.20%から0.45%と2倍に増えています。
これはナンピンによって損切り時の損失が大きくなってしまうことが原因です。

このように、ナンピンはプロフィットファクターや取引回数を増やせる反面、大きく負けてしまうリスクをはらんでいます。

注意点

今回のバックテストでは、スプレッドを固定で行っています。
日本時間早朝の相場が緩やかになる時間帯は、スプレッドが広がりやすいという特徴があります。
実運用を行うには、スプレッド制限をかけるなど、十分にスプレッドに十分注意しましょう。

また、マーチンゲールは自己資金を一瞬で失ってしまうリスクがあるので、運用にも注意が必要です。

プログラム全文


#property copyright "Copyright(C) 2023, OANDA"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

input int                   MAGICMA = 23498721;                         // マジックナンバー
input double                Lots =0.01;                                 // 1ロット十万通貨単位
input int                   Slippage = 4;                               // エントリー見送りスリッページ
input double                MaxSpread = 5;                              // エントリー見送りスプレッド
input double                TakeProfit = 10.0;                          // 利益確定幅(pips)
input double                LossCut = 20.0;                             // 損切確定幅(pips)
input int                   RSIPeriod=6;                                // 期間
input   ENUM_APPLIED_PRICE  RSIAppliedPrice = PRICE_CLOSE;              // 適用価格
input int                   UpLine = 85;                                // 上の線
input int                   DownLine = 25;                              // 下の線
input int                   TradeTime = 0;                              // トレードを行う時間

//ナンピン設定
input int                   NanpinWidth = 5;                            //ナンピンの幅(pips)
  
double dSpread;

int OnInit()
{
   return(INIT_SUCCEEDED);
}
void OnTick()
{
   dSpread = (Ask - Bid) / (Point * 10);
   
   if(CalculateCurrentOrders()==0 && dSpread < MaxSpread) CheckForOpen(); 
   if(CalculateCurrentOrders()==1 && dSpread < MaxSpread) CheckForNanpin(); 
   CheckForClose();
}
void CheckForOpen()
{
   int res;
   double RSI = iRSI(Symbol(), 0, RSIPeriod, RSIAppliedPrice, 1);
   if(TradeTime == TimeHour(Time[1]))
   {
      if(RSI < DownLine)
      {
         res=OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0,"", MAGICMA, 0, Red);
      }
      if(RSI > UpLine)
      {
         res=OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, "", MAGICMA, 0, Blue);
      }
   }
}
void CheckForNanpin()
{
   int res;
   double buyProfitPips = 0;
   double SellProfitPips = 0;
   
   for( int i=0; i<OrdersTotal(); i++ ){
      if( OrderSelect(i, SELECT_BY_POS) == true && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
         if( OrderType() == OP_BUY ){
            buyProfitPips = (MarketInfo(Symbol(),MODE_BID) - OrderOpenPrice()) / (Point * 10); 
            break;
         }else if( OrderType() == OP_SELL ){
            SellProfitPips = (OrderOpenPrice() - MarketInfo(Symbol(),MODE_ASK)) / (Point * 10);
            break;
         }  
      }
   }
   
   if(SellProfitPips == 0 && buyProfitPips < NanpinWidth * -1)
   {
      res=OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0,"", MAGICMA, 0, Red);
   }
   if(buyProfitPips == 0 && SellProfitPips < NanpinWidth * -1)
   {
      res=OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0,  0, "", MAGICMA, 0, Blue);
   }

}
void CheckForClose(){
   int res;
   double buyProfitPips = 0;
   double SellProfitPips = 0;
   
   for( int i=0; i<OrdersTotal(); i++ ){
      if( OrderSelect(i, SELECT_BY_POS) == true && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
         if( OrderType() == OP_BUY ){
            buyProfitPips = (MarketInfo(Symbol(),MODE_BID) - OrderOpenPrice()) / (Point * 10); 
         }else if( OrderType() == OP_SELL ){
            SellProfitPips = (OrderOpenPrice() - MarketInfo(Symbol(),MODE_ASK)) / (Point * 10);
         }  
      }
   }
   if(TakeProfit <= buyProfitPips || LossCut <= buyProfitPips * -1){
      for( int i=OrdersTotal()-1; i>=0; i-- ){
         if( OrderSelect(i, SELECT_BY_POS) == true ){
            if( OrderType() == OP_BUY && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
               res = OrderClose(OrderTicket(), OrderLots(), Bid, Slippage * 10,Green);
            }
         }
      }
   }
   if(TakeProfit <= SellProfitPips || LossCut <= SellProfitPips * -1){
      for( int i=OrdersTotal()-1; i>=0; i-- ){
         if( OrderSelect(i, SELECT_BY_POS) == true ){
            if( OrderType() == OP_SELL && OrderMagicNumber() == MAGICMA && OrderSymbol() == Symbol() ){
               res = OrderClose(OrderTicket(),OrderLots(),Ask,Slippage * 10,Green);
            }
         }
      }
   }
}
int CalculateCurrentOrders()
{
   int positions = 0;
   for(int i=0;i<OrdersTotal();i++)
   {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
      {
      positions++;
      }
   }
   return positions;
}

EA(自動売買)を学びたい方へオススメコンテンツ

EA運用の注意点

OANDAではEA(自動売買)を稼働するプラットフォームMT4/MT5の基本的な使い方について、画像や動画付きで詳しく解説しています。MT4/MT5のインストールからEAの設定方法までを詳しく解説しているので、初心者の方でもスムーズにEA運用を始めることが可能です。またOANDAの口座をお持ちであれば、独自開発したオリジナルインジケーターを無料で利用することもできます。EA運用をお考えであれば、ぜひ口座開設をご検討ください。


本ホームページに掲載されている事項は、投資判断の参考となる情報の提供を目的としたものであり、投資の勧誘を目的としたものではありません。投資方針、投資タイミング等は、ご自身の責任において判断してください。本サービスの情報に基づいて行った取引のいかなる損失についても、当社は一切の責を負いかねますのでご了承ください。また、当社は、当該情報の正確性および完全性を保証または約束するものでなく、今後、予告なしに内容を変更または廃止する場合があります。なお、当該情報の欠落・誤謬等につきましてもその責を負いかねますのでご了承ください。

この記事をシェアする

ホーム » FX自動売買基礎と応用 » 朝スキャEAにナンピンを実装するカスタマイズ【コピペで使用可能】