固定pipsのトレーリングストップは、
値動きの大きい通貨ペアでは損切りが近すぎてすぐ決済され、
値動きの小さい通貨ペアでは逆に遠すぎて含み益を守れない、
ということが起こりがちです。
既刊のパラボリックSARをトレーリングストップに使うEAの記事では、
「今より有利な位置にしか損切りを動かさない」というガード条件を使いましたが、
この記事ではトレーリングの「幅」そのものを、
ATR(平均値幅)×倍率で自動的に調整する設計にします。
・固定pipsトレーリングの弱点とATRで解決する発想
・iATR()でトレーリング幅を計算する方法
・『近づける方向にしか動かさない』ガード条件の書き方
・パラボリックSAR版との設計思想の違い
設計思想: なぜATRでトレーリング幅を決めるのか
まず、固定pipsではなくATRを使う理由を整理します。
固定pipsのトレーリングの弱点
「含み益が出たら50pips離れたところに損切りを動かす」という固定pipsの設計は、
実装はシンプルですが、通貨ペアや相場の状況によって、
50pipsという幅が「近すぎる」場合と「遠すぎる」場合の両方が起こります。
ATRをトレーリング幅に使う発想
ATRは直近の値動きの大きさそのものを表す指標です。
損切り幅を「固定の数字」ではなく「今の値動きの何倍か」で決めれば、
通貨ペアや相場の状況が変わっても、同じ考え方でトレーリング幅を調整できます。
既刊パラボリックSAR版との違い
| 項目 | パラボリックSAR版 | 本記事のATR版 |
|---|---|---|
| トレーリングの根拠 | SARの値そのもの(価格に追従する点) | 現在値からATR×倍率だけ離れた位置 |
| 幅の変化 | SARの加速係数に従って自動で変わる | ATRの値に比例して変わる |
| 調整できる要素 | Step・Maximum(加速の仕方) | ATR期間・倍率(値幅そのもの) |
エントリー条件: シンプルな移動平均クロス
この記事の主題はトレーリングストップの実装です。
エントリー条件自体は複雑にせず、既刊記事群と同じシンプルな移動平均クロスを使います。
確定足(1本前)で判定する
int GetMaCrossSignal()
{
double fastPrev = iMA(NULL, 0, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double slowPrev = iMA(NULL, 0, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double fastPrev2 = iMA(NULL, 0, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
double slowPrev2 = iMA(NULL, 0, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
if(fastPrev2 <= slowPrev2 && fastPrev > slowPrev)
return(1);
if(fastPrev2 >= slowPrev2 && fastPrev < slowPrev)
return(-1);
return(0);
}
形成中の0番目の足ではなく、確定済みの1本前・2本前の足で判定することで、
既刊記事群と同じくリペイントを避けています。
ポジション重複を防ぐチェック
int GetMyTicket()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagic)
return(OrderTicket());
}
return(-1);
}
同じ通貨ペア・同じマジックナンバーのポジションが既にある場合は、
新規エントリーをスキップします。
iATR関数とストップ幅の計算
トレーリング幅の計算に使うiATR()の使い方を確認します。
iATR()のシグネチャ
double iATR( string symbol, //通貨ペア(NULLで現在のチャート) int timeframe, //時間足(0で現在の時間足) int period, //計算期間 int shift //シフト(0が最新バー) );
既刊のiATR関数の解説記事で確認済みのシグネチャです。
ATR×倍率でストップ幅を決める
double GetTrailingDistance()
{
double atr = iATR(_Symbol, 0, InpAtrPeriod, 0);
return(atr * InpAtrMultiplier);
}
形成中の足(shift=0)のATRを使うことで、
トレーリング幅が直近の値動きにあわせて毎ティック更新されます。
InpAtrMultiplierをinputにする理由
・倍率を小さくするほど損切りが早く近づき、利益を確定しやすくなる反面
・ダマシで決済される頻度が増える
・倍率を大きくするほど値動きに余裕を持たせられる反面
・含み益を手放すまでの戻りが大きくなる
・まずは既定値2.0前後から試し、通貨ペアや時間足に合わせて調整する
トレーリング関数: 近づける方向にしか動かさないガード
既刊パラボリックSAR版と同じ設計思想を、ATRベースの距離計算に置き換えます。
新しいSL候補の計算(買いポジションの例)
double distance = GetTrailingDistance();
double minDistance = MarketInfo(_Symbol, MODE_STOPLEVEL) * Point;
double candidate = Bid - distance;
bool improves = (candidate > OrderStopLoss());
bool farEnough = (Bid - candidate >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket, OrderOpenPrice(), candidate, OrderTakeProfit(), 0, clrLimeGreen))
Print("買いポジションのSL更新に失敗しました。エラーコード=", GetLastError());
売りポジションの場合は、
`Ask + distance`を候補にし、
不等号の向きを反転させるだけで同じ考え方が使えます
(完全なコードは次の節を参照してください)。
improves/farEnoughガードの意味
`improves`は「新しいSL候補が、今の損切りより有利な位置かどうか」、
`farEnough`は「ブローカーの最小ストップ距離を満たしているかどうか」、
を確認します。
既刊パラボリックSAR版と同じく、この2つを両方満たしたときだけ更新します。
OrderModify失敗時の挙動
OrderModify()が失敗しても、次のティックで同じ条件を再計算し直すだけなので、
EA自体が停止することはありません。
エラーコードをPrint()で出力し、原因を追えるようにしておきます。
実際に動くコード全体
移動平均クロスでエントリーし、ATR×倍率の距離でトレーリングストップを行う、
実際にMetaEditorでコンパイルできるEAです。
//+------------------------------------------------------------------+
//| AtrTrailingStopEA.mq4 |
//| 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
input int InpFastPeriod = 5; //短期MA期間
input int InpSlowPeriod = 20; //長期MA期間
input int InpAtrPeriod = 14; //ATR計算期間
input double InpAtrMultiplier = 2.0; //トレーリング幅の倍率
input double InpLots = 0.1; //発注ロット数
input int InpMagic = 20261024; //マジックナンバー
int GetMaCrossSignal()
{
double fastPrev = iMA(NULL, 0, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double slowPrev = iMA(NULL, 0, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double fastPrev2 = iMA(NULL, 0, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
double slowPrev2 = iMA(NULL, 0, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
if(fastPrev2 <= slowPrev2 && fastPrev > slowPrev)
return(1);
if(fastPrev2 >= slowPrev2 && fastPrev < slowPrev)
return(-1);
return(0);
}
int GetMyTicket()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagic)
return(OrderTicket());
}
return(-1);
}
double GetTrailingDistance()
{
double atr = iATR(_Symbol, 0, InpAtrPeriod, 0);
return(atr * InpAtrMultiplier);
}
void TrailStopWithAtr(int ticket)
{
if(!OrderSelect(ticket, SELECT_BY_TICKET)) return;
double distance = GetTrailingDistance();
double minDistance = MarketInfo(_Symbol, MODE_STOPLEVEL) * Point;
if(OrderType() == OP_BUY)
{
double candidate = Bid - distance;
bool improves = (candidate > OrderStopLoss());
bool farEnough = (Bid - candidate >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket, OrderOpenPrice(), candidate, OrderTakeProfit(), 0, clrLimeGreen))
Print("買いポジションのSL更新に失敗しました。エラーコード=", GetLastError());
}
if(OrderType() == OP_SELL)
{
double candidate = Ask + distance;
bool improves = (candidate < OrderStopLoss());
bool farEnough = (candidate - Ask >= minDistance);
if(improves && farEnough)
if(!OrderModify(ticket, OrderOpenPrice(), candidate, OrderTakeProfit(), 0, clrTomato))
Print("売りポジションのSL更新に失敗しました。エラーコード=", GetLastError());
}
}
void OnTick()
{
int ticket = GetMyTicket();
if(ticket < 0)
{
int signal = GetMaCrossSignal();
double distance = GetTrailingDistance();
if(signal == 1)
{
double sl = Ask - distance;
int result = OrderSend(_Symbol, OP_BUY, InpLots, Ask, 3, sl, 0,
"AtrTrailStop Buy", InpMagic, 0, clrBlue);
if(result < 0)
Print("買い注文に失敗しました。エラーコード=", GetLastError());
}
else if(signal == -1)
{
double sl = Bid + distance;
int result = OrderSend(_Symbol, OP_SELL, InpLots, Bid, 3, sl, 0,
"AtrTrailStop Sell", InpMagic, 0, clrRed);
if(result < 0)
Print("売り注文に失敗しました。エラーコード=", GetLastError());
}
}
else
{
TrailStopWithAtr(ticket);
}
}
利確(TP)をあえて0にしている理由
このEAは発注時の利確(TP)を0(無効)にし、
利益を伸ばす判断をすべてトレーリングストップに任せる設計にしています。
固定のTPを決め打ちしたい場合は、OrderSendの該当引数に値を渡してください。
MetaEditorでのコンパイル・実行手順
- MetaEditorで新規Expert Advisor「AtrTrailingStopEA」を作成し、上のコードを貼り付ける
- F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- デモ口座のチャートにドラッグ&ドロップで適用する
- ストラテジーテスターで、含み益が伸びるほど損切りラインがATR×倍率の幅を保ったまま近づいていく様子を確認する
よくあるエラーと注意点
実際に運用する前に確認しておきたい点をまとめます。
ATR期間を短くしすぎるとチャタリング
・InpAtrPeriodを極端に短くすると、ATRの値が細かく上下しやすくなる
・トレーリング幅が毎ティック大きく変わり、早期に損切りされやすくなる
・まずは既定値14から試し、通貨ペアに合わせて調整する
OrderModifyがエラー130で失敗する
ATR×倍率で計算した距離が、
ブローカーの最小ストップ距離(MODE_STOPLEVEL)より近い場合、
OrderModify()はエラー130(Invalid stops)で失敗します。
`farEnough`のガードはこれを防ぐためのものですが、
倍率を極端に小さくするとガード自体が機能しなくなる点に注意してください。
バックテストと実運用でトレーリングの回数が変わる
`iATR(_Symbol, 0, InpAtrPeriod, 0)`は、
形成中の足の値を毎ティック取得するため、
ティックの発生頻度(実運用とテスターのモデル)によって、
更新される回数そのものが変わります。
MQL4とMQL5の違い
ATRトレーリングの考え方を、MQL5に移植する場合の違いを確認します。
MQL4は値を直接返す方式
MQL4の`iATR()`は、呼び出すたびに1つの値を直接返します。
MQL5はハンドル方式に変わる
MQL5には`iATR()`という同名関数はなく、
テクニカル指標ハンドルを作成し、
`CopyBuffer`で値を取得する方式に変わります。
発注・決済変更もMQL5では、
`CTrade`クラスの`PositionModify()`を使う設計になります。
ガード条件の考え方はそのまま応用できる
「今より有利な位置にしか動かさない」というガード条件自体は、
MQL5に移植する場合でもそのまま応用できます。
よくある質問
ATRトレーリングストップEAについて、よくある疑問に回答します。
・倍率(InpAtrMultiplier)は通貨ペアや時間足ごとにバックテストで調整する
・利確を固定したい場合はOrderSendのtp引数に値を渡す
・パラボリックSAR版と同時に同じ口座で動かす場合はマジックナンバーを分ける
パラボリックSAR版とどちらを使うべきですか?
どちらが優れているというより、根拠にしている指標が異なります。
価格そのものの位置を根拠にしたいならパラボリックSAR版、
値動きの大きさを根拠にしたいならATR版を選んでください。
固定pipsのトレーリングと何が根本的に違いますか?
固定pipsは通貨ペアや相場の状況によらず同じ幅ですが、
ATR版は値動きが大きいときは幅を広げ、小さいときは幅を狭める点が異なります。
利確(TP)もATRで動かすことはできますか?
できます。
本記事のコードは損切りのみをトレーリングしていますが、
`OrderModify()`の第4引数(takeprofit)にも、
同じようにATRベースの候補値を渡せば、
利確ラインを動かす設計に拡張できます。
【MQL4】ATRの値でトレーリングストップ幅を自動調整するEAの作り方(パラボリックSAR版との違い):まとめ
固定pipsのトレーリングストップは、
通貨ペアや相場の状況によって近すぎたり遠すぎたりすることがあります。
ATR×倍率でトレーリング幅を自動調整し、
「今より有利な位置にしか動かさない」というガード条件と組み合わせることで、
値動きの大きさにあわせた損切り管理ができます。
パラボリックSAR版とは根拠にする指標が異なるだけで、設計思想は共通しています。
コメント