含み益が伸びていたのに、反転して結局マイナスで決済になった、という経験はないでしょうか。

損切り(SL)を置いていても、それは「最大でここまでの損失を受け入れる」

というラインでしかなく、いったんプラスに転じた利益を守ってはくれません。

この記事では、含み益が一定以上伸びたら、

SLをエントリー価格(建値)まで自動で動かす処理を実装します。

この処理は「建値ストップ(ブレイクイーブンストップ)」と呼ばれ、

MQL5では`PositionModify`を使って実装します。

発動タイミングは固定pipsにせず、2つの考え方で判定します。

1つはATR(相場の変動幅)、もう1つはRRR(リスクリワード比)です。

建値ストップとは何か、なぜ固定pipsでは不十分なのか

建値ストップとは、ポジションの含み益が一定水準まで伸びた時点で、

SLをエントリー価格(建値)まで引き上げる(売りなら引き下げる)処理です。

これにより、その後に相場が反転しても被害を抑えられます。

最悪の結果は「損益ゼロ」、実際にはスプレッド分だけ小さいマイナス、に留まります。

要点: 建値ストップは

「利益を伸ばす」ための仕掛けではなく、

「一度乗った含み益をゼロ以下にしない」

ための守りの仕掛けです。

固定pips(例えば「30pips伸びたら建値」)で実装するのが最も簡単ですが、

通貨ペアやその日のボラティリティを無視した一律の数字になりがちです。

値動きの荒い相場では早すぎる建値移動でノイズにより損切りさせられ、

値動きの細かい相場では遅すぎて利益の大半を吐き出してから発動する、

という問題が起きます。

MQL4のトレーリングストップとの違い

トレーリングストップは、価格が伸びるたびにSLを継続的に追随させる仕組みです。

建値ストップは「1回だけ、建値というピンポイントの位置に動かす」

という点でシンプルであり、トレーリングストップの前段階(まずゼロリスクを確保してから、

その後トレーリングに切り替える)として組み合わせて使われることが多い設計です。

判定に使うMQL5関数: PositionGetDouble・PositionGetInteger

建値ストップの判定には、まず現在のポジション情報を取得する必要があります。

if(PositionSelect(_Symbol))
  {
   double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);//建値
   double currentSl  = PositionGetDouble(POSITION_SL);        //現在のSL
   double currentTp  = PositionGetDouble(POSITION_TP);        //現在のTP
   long   posType    = PositionGetInteger(POSITION_TYPE);     //POSITION_TYPE_BUYかPOSITION_TYPE_SELLか
  }

`PositionSelect(_Symbol)`は、

指定した通貨ペアの保有ポジションを、

以後の`PositionGetDouble()`等の対象として選択する関数です。

ポジションがなければ`false`を返します。

現在価格の取得にはSymbolInfoDoubleを使う

含み益の判定には、現在の市場価格も必要です。

買いポジションならBid(売値)、売りポジションならAsk(買値)で決済されるため、

ポジションの方向によって参照する価格を使い分けます。

double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double currentPrice = (posType == POSITION_TYPE_BUY) ? bid : ask;

発動条件の設計: ATR方式とRRR方式

固定pipsの弱点を避けるため、この記事では2つの発動条件を用意し、

`input`パラメーターで切り替えられるようにします。

ATR方式: 相場の変動幅に合わせる

ATR(平均真の値幅)は、

その通貨ペア・その時間軸で1本の足が、

平均してどれだけ動いているかを表す指標です。

「ATRの何倍伸びたら建値に動かすか」という考え方にすると、

値動きが荒い相場では発動ラインが自動的に広がります。

その結果、ノイズによる早すぎる発動を避けやすくなります。

int atrHandle = iATR(_Symbol, PERIOD_CURRENT, AtrPeriod);
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
CopyBuffer(atrHandle, 0, 1, 1, atrBuffer);//1つ前の確定足のATR
double atrTriggerDistance = atrBuffer[0] * AtrMultiplier;

RRR方式: 最初のリスク幅を基準にする

RRR(リスクリワード比)方式は、

エントリー時に置いた損切り幅を基準にします。

損切り幅とは、建値から最初のSLまでの距離のことです。

「そのリスク幅の何倍の利益が乗ったら建値に動かすか」を判定します。

例えば「リスク1に対して利益1(1R)伸びたら建値」という設計であれば、

最悪でも損失ゼロ以下にはならない、という状態を早い段階で確保できます。

補足: この方式には、

最初のSL位置(初期リスク幅)を、

どこかに保存しておく必要があるという前提があります。

EAの新規発注時にグローバル変数(`GlobalVariableSet`)や、

ポジションのコメント欄に初期SL幅を記録しておく設計が実務的です。

この記事のサンプルコードでは、簡略化のため「現在のSLと建値の距離」

を初期リスク幅とみなして計算します(発注直後にSLを動かしていない前提です)。

SLを書き換える: CTrade::PositionModify

発動条件を満たしたら、実際にSLを建値まで動かします。

MQL5の標準ライブラリの`CTrade`クラスを使うと、

生の`OrderSend()`で構造体を組み立てる手間なく1行で書けます。

#include <Trade\Trade.mqh>
CTrade trade;

if(!trade.PositionModify(_Symbol, openPrice, currentTp))
  {
   PrintFormat("PositionModifyが失敗しました。retcode=%d (%s)",
               trade.ResultRetcode(), trade.ResultRetcodeDescription());
  }

`PositionModify(symbol, sl, tp)`は、

新しいSLとTPの価格をそのまま渡す関数です。

TPを変更する必要がない場合は、取得済みの`currentTp`をそのまま渡し直すことで、

TPだけは変更されないようにします。

注意: すでにSLが建値以上(買いの場合)に、

動いている状態で再度同じ処理を呼ぶと、

無駄なPositionModifyが発生します。

「現在のSLがまだ建値未満か」を条件に加え、1回だけ発動するようにする必要があります。

実際に動くコード全体(建値ストップEA)

ここまでの内容をまとめた、含み益がATRまたはRRRの条件を満たしたらSLを建値へ動かす、

実際にMetaEditorでコンパイルできるEAです。

//+------------------------------------------------------------------+
//|                                        BreakevenStop.mq5           |
//|                     Copyright 2026, FX-EA System Project Creator |
//|                        https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link      "https://creator.fx-ea-system-project.com/"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

enum TRIGGER_MODE
  {
   TRIGGER_ATR = 0,//ATR倍率で判定
   TRIGGER_RRR = 1//リスク幅の倍率(R)で判定
  };

input TRIGGER_MODE Mode         = TRIGGER_ATR;//発動条件の方式
input int          AtrPeriod    = 14;//ATR方式: ATR期間
input double        AtrMultiplier = 1.5;//ATR方式: ATRの何倍で発動するか
input double        RrrMultiplier = 1.0;//RRR方式: 初期リスク幅の何倍(何R)で発動するか
input int          BufferPoints = 20;//建値からさらに何ポイント上乗せするか(スプレッド対策)

CTrade trade;
int    atrHandle;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   atrHandle = iATR(_Symbol, PERIOD_CURRENT, AtrPeriod);
   if(atrHandle == INVALID_HANDLE)
     {
      Print("iATRのハンドル取得に失敗しました。");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(!PositionSelect(_Symbol))
      return;

   double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double currentSl = PositionGetDouble(POSITION_SL);
   double currentTp = PositionGetDouble(POSITION_TP);
   long   posType   = PositionGetInteger(POSITION_TYPE);

   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double currentPrice = (posType == POSITION_TYPE_BUY) ? bid : ask;

   double triggerDistance = 0;
   if(Mode == TRIGGER_ATR)
     {
      double atrBuffer[];
      ArraySetAsSeries(atrBuffer, true);
      if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) <= 0)
         return;
      triggerDistance = atrBuffer[0] * AtrMultiplier;
     }
   else
     {
      double initialRisk = MathAbs(openPrice - currentSl);
      if(initialRisk <= 0)
         return;//SL未設定の場合はRRR方式では判定できない
      triggerDistance = initialRisk * RrrMultiplier;
     }

   double buffer = BufferPoints * _Point;

   if(posType == POSITION_TYPE_BUY)
     {
      bool alreadyBreakeven = (currentSl >= openPrice);
      bool reached = (currentPrice - openPrice) >= triggerDistance;
      if(!alreadyBreakeven && reached)
        {
         if(!trade.PositionModify(_Symbol, openPrice + buffer, currentTp))
            PrintFormat("PositionModify失敗。retcode=%d (%s)",
                        trade.ResultRetcode(), trade.ResultRetcodeDescription());
        }
     }
   else
     {
      bool alreadyBreakeven = (currentSl <= openPrice && currentSl != 0);
      bool reached = (openPrice - currentPrice) >= triggerDistance;
      if(!alreadyBreakeven && reached)
        {
         if(!trade.PositionModify(_Symbol, openPrice - buffer, currentTp))
            PrintFormat("PositionModify失敗。retcode=%d (%s)",
                        trade.ResultRetcode(), trade.ResultRetcodeDescription());
        }
     }
  }
//+------------------------------------------------------------------+

MetaEditorで実際に動かす手順

  1. MT5のMetaEditorで「ファイル」→「新規作成」→「エキスパートアドバイザー(テンプレートから)」を選ぶ
  2. 「BreakevenStop」という名前で作成し、テンプレートの中身をすべて削除して上のコードを貼り付ける
  3. F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
  4. デモ口座でチャートに適用し、保有ポジション(SLを設定済み)がある状態でOnTickが動くことを確認する
  5. 「表示」→「ツールボックス」の「取引」タブで、SLが建値付近まで動いたことを確認する

よくあるエラーと対処

  • コンパイルエラー「’Trade.mqh’ file not found」: `#include `のパス区切りがバックスラッシュになっているか確認してください
  • PositionModifyがretcode 10016(Invalid stops)で失敗する: ブローカーが定める最小ストップ距離(`SYMBOL_TRADE_STOPS_LEVEL`)より建値+バッファが現在価格に近すぎる可能性があります。`BufferPoints`を増やすか、`SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)`で確認してください
  • RRR方式で何も発動しない: SLを設定していないポジションでは初期リスク幅が計算できず判定をスキップする仕様です。発注時に必ずSLを設定してください
  • 建値に動いたはずなのにマイナスで決済された: スプレッドと`BufferPoints`の設定を確認してください。バッファが0だと、スプレッド分だけ建値より不利な価格で約定することがあります

MQL4で同じことをする場合の違い

MQL4では`PositionModify`は存在しません。

代わりに`OrderModify()`でSLを書き換えます。

MQL4は`OrderSelect()`でチケットを選んだ後、

`OrderOpenPrice()`や`OrderStopLoss()`で、

建値・現在SLを取得する流れになります。

関数名は違えど、設計思想は同じです。

ATR取得についても、MQL4版の`iATR()`は、

値をその場で返す仕様です(ハンドル不要)。

そのぶん、この記事のコードよりシンプルに書けます。

建値ストップを使う際の注意点と限界

建値ストップは「損失をゼロ以下に抑える」ための仕組みであり、

利益を伸ばすための仕組みではありません。

発動直後に価格が反転して建値ちょうどで決済されると、「あと少し待てば利益が伸びていたのに」

という結果になることもあります。

向いている場面: ボラティリティが高く、含み益が急激に消えるリスクが高い相場

向いていない場面: 値動きが穏やかで、

ダマシの反転が多い相場(早すぎる発動で、

トレンド初期に振り落とされやすい)

よくある質問

建値ストップの実装でよく検索されている疑問を、3つにまとめて回答します。

TPも一緒に変更する必要がありますか?

いいえ、この記事のコードでは`currentTp`を取得してそのまま渡し直しているため、

TPは変更されません。

SLだけを更新したい場合は、

必ず元のTP値を取得して渡し直してください。

(0を渡すとTPが削除されます)

複数ポジションを同時に保有している場合はどうなりますか?

この記事のコードは`PositionSelect(_Symbol)`で、

通貨ペア単位に1ポジションを想定しています。

ヘッジ口座で同一銘柄に複数ポジションがある場合は、

`PositionGetTicket()`でチケットを1つずつ取得し、

`PositionSelectByTicket()`とチケット版の、

`PositionModify()`をループで呼ぶ設計に変更してください。

ATR方式とRRR方式、どちらを使うべきですか?

通貨ペアやSL幅を頻繁に変えずに運用するなら、

値動きに応じて発動ラインが自動調整されるATR方式が扱いやすいです。

案件ごとにリスクリワードを厳密に管理したいEAでは、RRR方式の方が「何R伸びたか」

という指標と一致するため設計意図が明確になります。

【MQL5入門】含み益が乗ったらSLを建値へ動かす。PositionModifyで作る「建値ストップ」の実装(ATR・RRRベース):まとめ

この記事では、含み益が一定水準まで伸びたら、

SLをエントリー価格(建値)まで動かす「建値ストップ」を、

MQL5の`PositionModify`で実装する方法を解説しました。

固定pipsではなく、ATR(相場の変動幅)または、

RRR(初期リスク幅の倍率)で発動タイミングを判定することで、

通貨ペアやその日のボラティリティに応じた柔軟な設計にできます。

建値ストップは万能ではありません。

早すぎる発動でトレンド初期に振り落とされるリスクもあるため、

この記事の`AtrMultiplier`や`RrrMultiplier`を、

自分の手法のバックテストに合わせて調整してください。

関連記事