ネットで見つけたRSIの逆張りEAサンプルコードをそのままMT4に入れて動かしたら、他のEAや裁量トレードのポジションを持っているだけでエントリーが止まってしまった、EAを再起動したらポジションを見失って二重にエントリーしてしまった——RSIを使ったEAでよくあるつまずきポイントです。

原因の多くは、コード自体は動く(コンパイルは通る)のに、「自分のEAが出したポジションだけを判別する処理」が抜けていることです。この記事では、RSIが30を下回った後に再び30を上抜けたタイミングでロングエントリーし、RSIが70に達したら決済するEAを例に、パラメーターの設定からOnTick()関数の中身、そして見落としがちな「ポジション管理」と「損切り設定」まで、実際にMetaEditorでコンパイルしてMT4で動かせる状態のコードを最後まで解説します。

RSIで30を下回り上抜けでロングエントリーするEA

RSIで30を下回り上抜けでロングエントリーするEAを作成していきたいと思います。

メタエディタ(MetaEditor)を立ち上げる

MT4を起動し、ツールバーの黄色いボタン(またはツール→MetaQuotes Language Editor)からメタエディタ(MetaEditor)を立ち上げましょう。

新しいEAを作るので、メタエディタの「ファイル」→「新規作成」を選び、ウィザードで「Expert Advisor(テンプレートを使用)」を選択して「次へ」を押します。

名前を入力する画面が出るので、今回は「RSI-30」と入力します(作者名・リンク・バージョンは空欄のままで問題ありません)。あとの画面はイベントハンドラのチェックを何も入れず、そのまま「次へ」を数回押して「完了」まで進めてください。すると、OnInit()・OnDeinit()・OnTick()の3つの関数だけが最初から書かれた空のひな形が開きます。

このひな形に、これから説明するコードを追記していきます。

パラメーターを記述する

ロット数・損切り・スリッページ・マジックナンバーを、あとから値を変更しやすいようにinput変数として用意します。

input double Lots        = 0.1;//ロット数
input int    StopLoss    = 20;//損切り(pips)
input int    Slippage    = 3;//スリッページ
input int    MagicNumber = 1234;//マジックナンバー
Copy

MagicNumberは、このEAが出した注文だけを識別するための番号です。複数のEAを同じ口座で動かす場合は、それぞれ異なる値にしてください。

OnTick()関数を記述する

OnTick()関数を記述しましょう。

まず、エントリー条件です。現在のRSIが30より大きく、1つ前のRSIが30以下(=30を下回った状態から上に抜けた)場合にロングエントリーします。

if(iRSI(Symbol(),0,14,PRICE_CLOSE,0) > 30 && iRSI(Symbol(),0,14,PRICE_CLOSE,1) <= 30)

条件が合えば、ロングのポジションを持ちます。

OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,0,"EA",MagicNumber,0,Red);

決済は、RSIが70以上になった場合です。

if(iRSI(Symbol(),0,14,PRICE_CLOSE,0) >= 70)

条件が満たされていればポジションを決済します。

OrderClose(ticket,OrderLots(),Bid,Slippage,Yellow);

※元記事の解説文には「決済はRSIが70を超えた場合に(24期間のRSI)」という記載がありましたが、実際に掲載されていたコードは一貫して14期間のRSI(iRSI(Symbol(),0,14,PRICE_CLOSE,0))を使っていました。この記事では、実装コードに合わせて14期間に統一しています。エントリー判定と決済判定で異なる期間のRSIを使いたい場合は、決済用に別のinput変数を用意してください。

自分のポジションだけを管理する

元記事のコードは、OrdersTotal() == 0(口座内の注文数がゼロ)でエントリー判定し、OrdersTotal() > 0(1つ以上)で決済判定していました。これは「口座全体で見て注文が何もない/何かある」を見ているだけで、そのポジションが本当にこのEAが出したものかどうかを確認していません。

このため、同じ口座で他のEAを動かしていたり、裁量トレードでポジションを持っていたりすると、それだけでこのEAのエントリー判定が止まってしまいます。逆に決済判定では、他のEAのポジションを自分のチケット番号で誤って決済しようとしてエラーになることもあります。

さらに、元記事のTicketNumberはEA内だけで保持しているstatic変数のため、MT4を再起動したりEAを一度外して付け直したりすると値が失われます。ポジションを持ったまま再起動すると、次のOnTick()でポジションの存在に気づかず、新しいポジションを重ねて建ててしまいます。

これを防ぐため、自分(このマジックナンバー・この通貨ペア)のポジションを毎回探しにいく関数を用意します。

int GetMyTicket()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
         return(OrderTicket());
     }
   return(-1);
  }
Copy

この関数は、自分のポジションが見つかればそのチケット番号を、見つからなければ-1を返します。OnTick()のたびに口座の注文一覧から探し直すので、EAを再起動してもポジションを見失うことがありません。

決済するときは、このチケット番号をそのまま使わず、OrderSelect(ticket,SELECT_BY_TICKET)でもう一度そのチケットを選び直してからOrderClose()を呼びます。GetMyTicket()を呼んだ後の一瞬の間に、そのポジションが決済済みになっている可能性もゼロではないため、OrderClose()の直前に選び直すのが安全です。

損切り(ストップロス)を設定する

元のOrderSend()は、6番目の引数(損切り価格)が0になっていました。0を指定すると損切りは設定されず、RSIが70に達するまでポジションは決済されません。相場が予想と逆に動き続けた場合、含み損に歯止めがかかりません。

パラメーターに追加したStopLossを使って、エントリー方向と逆側に損切りラインを設定します(利確については、RSIが70に達するという条件そのものが利確ロジックを兼ねているため、TakeProfitの引数は元記事と同じく0のままにしています)。

Ask-StopLoss*Point*10

※Pointは通貨ペアの最小価格単位です。多くのFX業者が採用している5桁表示(例:USDJPYが150.123のように小数点以下3桁)のブローカーでは1pip=10Pointになるため、pips単位で指定したStopLossに10を掛けています。4桁表示のブローカーを使う場合は、この「×10」を外してください。

また、エラーコード130(Invalid stops)がログに出た場合、注文が失敗する原因のほとんどはStopLossの値がブローカーの最小ストップ距離より近すぎることです。その場合はStopLossの値を大きくしてみてください。

コンパイル

コンパイルしましょう。

MQL EA(エキスパートアドバイザー) RSI 30 上抜け

ソースコード全体

ここまでの修正をすべて反映した、実際にMetaEditorでコンパイルしてそのまま動かせる状態のコードです。

//+------------------------------------------------------------------+
//|                                                       RSI-30.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.1;//ロット数
input int    StopLoss    = 20;//損切り(pips)
input int    Slippage    = 3;//スリッページ
input int    MagicNumber = 1234;//マジックナンバー

//+------------------------------------------------------------------+
//| このEA(マジックナンバー・通貨ペア)のチケット番号を取得する         |
//+------------------------------------------------------------------+
int GetMyTicket()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
         return(OrderTicket());
     }
   return(-1);
  }

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int ticket = GetMyTicket();

   //ポジションを持っていない場合はエントリー判定
   if(ticket < 0)
     {
      if(iRSI(Symbol(),0,14,PRICE_CLOSE,0) > 30 && iRSI(Symbol(),0,14,PRICE_CLOSE,1) <= 30)//1つ前のRSIが30以下で現在のRSIが30を超えている場合
        {
         int newTicket = OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,0,"EA",MagicNumber,0,Red);//ロングエントリー
         if(newTicket < 0)
            Print("買い注文に失敗しました。エラーコード=",GetLastError());
        }
     }
   //ポジションを持っている場合は決済判定
   else
     {
      if(iRSI(Symbol(),0,14,PRICE_CLOSE,0) >= 70)//RSIが70以上の場合
        {
         if(OrderSelect(ticket,SELECT_BY_TICKET))
           {
            if(!OrderClose(ticket,OrderLots(),Bid,Slippage,Yellow))//ポジションを決済
               Print("決済に失敗しました。エラーコード=",GetLastError());
           }
        }
     }
  }
//+------------------------------------------------------------------+
Copy

バックテストで動作を確認する

コードを保存したら、MetaEditorの「コンパイル」(Ctrl+F7)で「0 error(s), 0 warning(s)」になることを確認します。

そのあとMT4の「表示」→「ストラテジーテスター」を開き、Expert AdvisorにRSI-30を選択、通貨ペアと期間を指定して、モデルを「全ティック(最も正確な値)」にしてスタートすると、過去データでの動作を確認できます。

修正前のコードでバックテストすると、口座に他のポジション(手動で建てた検証用ポジション等)がある状態ではエントリーが発生しないことを確認できます。修正後は、自分のポジションの有無だけで判定されるため、そうした影響を受けずに動作します。

実運用する前に確認しておきたいこと

このEAはRSIの単純な逆張り条件だけで売買する、学習用にシンプルへ作った例です。実際の口座で稼働させる前に、次の点を確認してください。

  • 強いトレンド相場ではRSIが30を割った後も下落し続けたり、70を超えた後も上昇し続けたりすることがあり(いわゆる「トレンドの一方通行」)、逆張りが機能しにくい
  • ロット数(Lots)は口座残高に対して過大にならないよう調整する
  • バックテストの結果は過去データに基づくものであり、将来の相場でも同じ成績になるとは限らない

まずはデモ口座、または少額のロットでのフォワードテスト(リアルタイムでの動作確認)を行ってから、本番運用を検討してください。

まとめ

RSIが30を下回った後に上抜けたタイミングでロングエントリーし、70に達したら決済するEAの作り方を、実際に動くコードとあわせて解説しました。エントリーのロジックだけでなく、自分のポジションだけを管理する処理と損切り設定まで含めて初めて、実運用を検討できる形になります。

今回はRSIを活用したEAの作り方でした。

関連記事