FX EA にATRストップロスを自動設定する方法|MQL5実装コードと倍数最適化
Photo by Maxim Hopman on Unsplash
最終更新: 2026年07月
固定pipsでストップロスを設定するEAは、ボラティリティが変化するたびに機能不全に陥る。静かな相場では過剰なリスクを取り、荒れた相場では短期的なノイズで即座にストップアウトされる——私はこの失敗を何度もバックテストで確認した。この問題を構造的に解決するのが、ATR(Average True Range)に基づく動的SL設定だ。本稿では、MQL5のiATR()関数を使ったハンドル方式の実装コードを完全解説し、時間足別の最適倍数選定からブローカーのSL距離制約への対応まで、実運用に必要な知識を体系的に整理する。なお、ATR-SLはリスク管理の一手段であり、損失を完全に防ぐものではない点を最初に明記しておく。
固定pipsのSLが機能しない理由を数字で確認する
直感的には「決まったpipsでSLを置けば管理しやすい」と感じるが、データは別の現実を示す。
Volatility Box Researchが595シンボル・2018年〜2025年を対象に実施した分析では、ATR 1.0倍以下のSLを設定した場合、65%超のケースで本来の相場方向に戻る前にストップアウトが発生していた。これは何を意味するか。エントリー根拠は正しくても、SLが狭すぎるために「正しいポジションが誤って刈られる」という事態が過半数を超えていたということだ。
同研究の比較データはより具体的だ。固定SLを採用した場合の最大ドローダウンは**-18.4%。これをATR 2.0倍のSLに切り替えると-11.7%**まで圧縮され、36%の改善が確認されている(Volatility Box Research、595シンボル 2018〜2025)。
プロのシステムトレーダーもこの問題には早くから対処していた。1983年に始まったタートルズシステムは、SLを「2N(ATRの2倍)」として定義した。これは相場ノイズの統計的特性から導かれた値であり、40年以上経過した今日のFX市場でも参照される基準値になっている。
固定SLが機能しない根本理由は単純だ。ボラティリティは時系列的に変動するが、固定値はその変動に追従できない。ATRはボラティリティの移動平均であるため、相場の状態に応じてSL幅が自動調整される。
Claudeと会話しながらインジケータが作れるHedgrow FXでは、ATR-SLの倍数検討やiATR()の実装コードをAIと対話しながら構築できる。
ATRの計算式と値の読み方
Photo by Markus Winkler on Unsplash
ATRはJ. Welles Wilderが1978年の著書『New Concepts in Technical Trading Systems』で提唱した指標だ(ATRとFXボラティリティの基礎概念も合わせて参照されたい)。計算の起点はTrue Range(TR)の定義にある。
True Rangeの定義(Wilder, 1978)
TR = max(
当日高値 - 当日安値,
|当日高値 - 前日終値|,
|当日安値 - 前日終値|
)
3つの候補の最大値を取ることで、ギャップを含む実質的な値動きの幅を捉える。前日終値との差を加えているのは、例えば大きなギャップアップが発生した場合に高値〜安値の幅だけでは実際のリスク範囲を過小評価するためだ。
ATRのスムージング計算
ATRはTRをWilderスムージングで平均化する。数式で表すと次のようになる。
ATR(t) = ATR(t-1) × (n-1)/n + TR(t) × 1/n [n=14がデフォルト]
これはα=1/14の指数移動平均(EMA)に相当する。単純移動平均とは異なり、直近の値動きに対してより敏感に反応する。
主要通貨ペアのATR実測値(2025年平均)
| 通貨ペア | 時間足 | ATR概算 | SL 2.0倍 |
|---|---|---|---|
| USDJPY | D1 | 133pips | 266pips |
| EURUSD | D1 | 75pips | 150pips |
| USDJPY | H4 | 約53pips(D1の40%) | 106pips |
| USDJPY | H1 | 約27pips(D1の20%) | 54pips |
出典:OffBeat Forex ADR Table(2025年平均値)
H4・H1のATRは時間足間のボラティリティ比率から推定した参考値だ。実運用では必ず自分のバックテストデータで実ATR値を確認してほしい。
「ATR×2倍をSLに使う」という慣行がある理由も同じデータが支える。前述のVolatility Box Researchによると、ATR 1.5倍では誤発動率38%、2.0倍では**21%**まで低下する。完全にゼロにはならないが、2.0倍という倍数は、ノイズによる誤ストップアウトと損失許容範囲のトレードオフとして現実的な着地点だ。私が2.0倍をデフォルトにしているのもこのデータを見てから判断した結果だ。
MQL5でATR-SLを実装する(完全コード)
MQL5でATR-SLを自動計算するには、iATR()でインジケーターハンドルを取得し、CopyBuffer()でATR値を引き出してSL幅を計算する3ステップが基本だ。MQL5プログラミング全般の基礎はMQL4/MQL5プログラミング入門を参照してほしい。
MQL4との違い:ハンドル方式の必須化
MQL4でのATR取得はiATR()が直接数値を返す方式だった。MQL5ではインジケーターハンドルを取得し、そのバッファからCopyBuffer()でデータを引き出す方式に変更されている。この点を理解していないとコンパイルエラーや実行時エラーに直結するため、最初に整理しておく。
| 項目 | MQL4 | MQL5 |
|---|---|---|
| ATR取得 | iATR()が直接double値を返す | iATR()がint型のハンドルを返す |
| バッファアクセス | 不要 | CopyBuffer()で取得 |
| リソース解放 | 不要 | OnDeinit()でIndicatorRelease()が必要 |
OnInit / OnTick / OnDeinit の3点セット実装
以下に完全な実装コードを示す。OnInit()でハンドルを初期化し、OnTick()でATR値を取得してSL/TPを計算する。
// ATRハンドルの宣言(グローバル変数)
int atrHandle;
input int InpATRPeriod = 14; // ATR計算期間
input double InpSLMultiplier = 2.0; // SL用ATR倍数
input double InpRRRatio = 1.5; // リスクリワード比
int OnInit()
{
atrHandle = iATR(_Symbol, PERIOD_CURRENT, InpATRPeriod);
if(atrHandle == INVALID_HANDLE)
{
Print("iATR初期化失敗: ", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
void OnTick()
{
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
if(CopyBuffer(atrHandle, 0, 0, 1, atrBuffer) < 1) return;
double atr = atrBuffer[0];
// ブローカー最小SL距離チェック
long stopLevelPts = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(stopLevelPts <= 0) stopLevelPts = 10;
double minDist = stopLevelPts * _Point;
double slDistance = MathMax(atr * InpSLMultiplier, minDist + 5 * _Point);
double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl_buy = NormalizeDouble(entryPrice - slDistance, _Digits);
double tp_buy = NormalizeDouble(entryPrice + slDistance * InpRRRatio, _Digits);
}
void OnDeinit(const int reason)
{
IndicatorRelease(atrHandle);
}
コードの要点解説
ArraySetAsSeries(atrBuffer, true):バッファのインデックスを「最新のバーが[0]」になるよう設定する。これを設定しないと配列の並び順が逆になる。
CopyBuffer(atrHandle, 0, 0, 1, atrBuffer):第2引数はバッファ番号(ATRは0番のみ)、第3引数は開始バー(0=最新)、第4引数は取得本数(1本)を指定する。戻り値が1未満の場合はデータ未取得として処理をスキップする。
SYMBOL_TRADE_STOPS_LEVEL:ブローカーが設定する最小SL距離(ポイント単位)だ。これを下回るSL設定はサーバーに拒否される。MathMax()でATR-SLと最小SL距離を比較し、大きい方を採用する実装が安全だ。この対応を実装していない記事やコードが多いが、スキャルピングEAや流動性の低い時間帯では実際に発注エラーが発生する。
NormalizeDouble(price, _Digits):価格の小数点桁数をシンボルの仕様に合わせて正規化する。これを省略すると「不正な価格」エラーが返ることがある。
ATR-SL倍数の最適化戦略
倍数選定は「ノイズ誤発動率」と「1回あたりの損失額」のトレードオフだ。Volatility Box Researchのデータを整理すると次の通りになる(595シンボル、2018〜2025年)。
| 倍数 | 誤発動率 | 想定SL幅(USDJPY D1) |
|---|---|---|
| 1.0x以下 | 65%超 | 133pips以下 |
| 1.5x | 38% | 約200pips |
| 2.0x | 21% | 約266pips |
時間足別の推奨倍数
スキャルピング(M1〜M15): 1.0x〜1.5x
短期足では1バーあたりのATR値が小さいため、倍数を上げすぎると1トレードあたりのリスクが大きくなりすぎる。ただし1.0x未満は65%超の誤発動率となるため、現実的な下限は1.0xだ。スキャルピングでSLを置かない設計は破綻への近道だ(ATR期間別スキャルピングEAの設定最適化も参照)。
デイトレード(M15〜H4): 1.5x〜2.5x
最も汎用性が高いレンジ。1.5xで誤発動率38%、2.0xで21%という差を考えると、エントリー精度に自信があるロジックなら2.0x基準でいい。私がデイトレードEAで使うデフォルトもここだ。
スイングトレード(H4〜D1): 2.0x〜3.0x
ポジション保有期間が長いほど、短期的なノイズに引っかかるリスクが高まる。D1足のATRを2.0x〜3.0xとすると、USDJPY換算で266〜400pipsのSL幅になる。資金管理上の1トレードリスク許容率(私の基準は総資金の1〜2%)との整合を確認する。
リスクリワード比(RR)との整合
SL幅が広がるほど同じRRを維持するためにはTPも遠くなる。ATR 2.0xのSLに対してRR 1:1.5のTPを設定した場合、ATR×3.0の距離がTPになる。相場環境によってはこの距離にTPが届かないことも多い。バックテスト時にRR比ごとの勝率と期待値を必ず確認する。この検証を飛ばしてパラメータを固定するのは危うい。
入力パラメータInpRRRatioの値を最適化するとき、単純に値を大きくすればいいわけじゃない。RRが高い設定は勝率が下がるため、期待値(勝率×平均利益 - 負け率×平均損失)を最大化する倍数を見つけることがゴールだ。
ATR-TP とトレーリングストップの実装
ATR-TPはSL倍数にRR比率を乗じた距離に設定する。トレーリングストップはOnTick()内でATR倍数を基準にSLを動的に更新する実装になる。
RR比率を使ったTPの設定
上記の基本コードではInpRRRatioをTPに乗じている。1:1.5のRRならInpRRRatio = 1.5と設定すると、SL幅の1.5倍の距離にTPが置かれる。
設定例:
- RR 1:1.5 →
InpRRRatio = 1.5(スキャルピング・デイトレード向け) - RR 1:2.0 →
InpRRRatio = 2.0(デイトレード・スイング向け) - RR 1:3.0 →
InpRRRatio = 3.0(トレンドフォロー型スイング向け)
ATRトレーリングストップの実装
エントリー後、利益が伸びるにつれてSLを追従させるトレーリングストップも、ATRを基準にすると動的に最適化できる。以下の実装をOnTick()内で呼び出す。
// ATRトレーリングストップ
CTrade trade;
void TrailingStop(double atr, double multiplier)
{
for(int i = PositionsTotal()-1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetSymbol(i) != _Symbol) continue;
double currentSL = PositionGetDouble(POSITION_SL);
double trailDist = atr * multiplier;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
double newSL = NormalizeDouble(
SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailDist, _Digits);
if(newSL > currentSL + _Point)
trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
else
{
double newSL = NormalizeDouble(
SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailDist, _Digits);
if(newSL < currentSL - _Point || currentSL == 0)
trade.PositionModify(ticket, newSL, PositionGetDouble(POSITION_TP));
}
}
}
このコードではPositionsTotal()で全ポジションをループし、対象シンボルのポジションのみSLを更新する。BUYポジションは現在のBIDからATR×倍数を引いた値、SELLポジションはASKにATR×倍数を加えた値をSLの候補とし、現在SLより有利な場合のみtrade.PositionModify()で更新する。
トレーリングストップとエントリー時ATR-SLを組み合わせる場合、エントリー時のSL幅とトレーリングの倍数を同一にするのが基本だ。倍数が異なると、エントリー直後にトレーリングが誤発動してSLが動く。私もこの挙動に一度足をすくわれた。
実装上の注意点とよくある失敗
ATR更新タイミングの誤処理・異常値局面での上限未設定・動的SL更新の副作用という3点が、ATR-SL実装で頻出する落とし穴だ。
ATR更新タイミングの問題
ATRはティックごとに変化する。OnTick()内でATRを毎回取得すると、同一バー内でSL値がわずかに変動し続ける副作用がある。エントリー判断には最新ATRを使い、一度エントリーしたらそのバーのATR値でSLをロックするのが安全だ。
バー確定後のATRを使う典型的な実装は次のパターンだ。
// バー確定を検知してATRを更新する例
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
static datetime lastBarTime = 0;
if(currentBarTime != lastBarTime)
{
lastBarTime = currentBarTime;
// バー確定時にATRを再取得してSLを更新
CopyBuffer(atrHandle, 0, 1, 1, atrBuffer); // [1]で1本前の確定バーを取得
}
CopyBuffer()の第3引数を1にすることで、確定済みの1本前バーのATR値を使う。これにより、バー途中の未確定ATR値に基づく誤計算を防げる。
ATRが異常に大きい局面への対応
経済指標発表直後や地政学的イベント時、ATRが平常時の2〜3倍に膨らむことがある。USDJPY D1のATRが200pipsを超えるような局面では、ATR 2.0倍のSLは400pipsになり、資金管理上の許容損失を大幅に超える可能性がある。
上限ATRを設けるコードを追加する対策が有効だ。
input double InpMaxATRPips = 150.0; // ATR上限(pips)
// ATR上限チェック(1pip = _Point * 10 for 5-digit brokers)
double pipSize = _Point * 10;
double atrPips = atr / pipSize;
double effectiveATR = MathMin(atr, InpMaxATRPips * pipSize);
double slDistance = MathMax(effectiveATR * InpSLMultiplier, minDist + 5 * _Point);
この実装はあくまで対処療法だ。ATRが急拡大している局面はリスクが根本的に高い。私の場合はニュースフィルターと時間フィルターを組み合わせた構成に変えて対処している。
ポジション保有中のSL自動更新の注意点
ATR-SLをエントリー時に設定し、その後変更しない設計(静的ATR-SL)と、ATRの変化に応じてSLを動的に更新する設計(動的ATR-SL)では挙動が大きく異なる。動的更新の場合、ボラティリティ低下局面ではSLが狭くなり、逆にボラティリティ拡大時にはSLが広がる。後者は既存ポジションのリスク管理を複雑化するため、バックテストと実運用のギャップが一番大きくなるのはここだ。動的ATR-SLを初めてフォワードで走らせたとき、想定外の損切り多発で初めてこの問題に気づいた。
バックテストでATR-SLの効果を検証する
固定SLとATR-SLの比較では、同等リスク幅を想定した条件を揃えた上で最大ドローダウン・早期ストップアウト率・プロフィットファクターを確認するのが標準的なアプローチだ。ATR-SLを実装したEAの具体的なバックテスト結果と検証手法についてはATR損切りEAのバックテスト検証も参照してほしい。
比較検証の設計方針
固定SL vs ATR-SLの比較バックテストを設計するとき、条件の公平性が結論を左右する。固定SLの幅は「ATR平均値×2.0倍」相当のpips数に設定する。例えばUSDJPY H4で平均ATRが53pipsならば、固定SLは106pipsに設定する。こうすることで「同等のリスク幅を想定した場合のパフォーマンス差」として比較できる。
検証で注目すべき指標は以下の通りだ。
- 最大ドローダウン:前述のデータでは固定SL-18.4% → ATR-SL-11.7%(36%改善)(Volatility Box Research)
- 早期ストップアウト率:同データでATR-SL採用により早期ストップアウトが34%削減されている
- 勝率:誤発動率の低下が直接的に勝率の改善につながる
- プロフィットファクター:全体的な損益の質
ATR期間14の根拠
デフォルト期間14はWilder(1978年)が推奨した値だ。「14日は月の半分に相当し、相場サイクルの半周期として統計的に安定した平均を提供する」というのがWilderの論拠だ。
期間を短くすると(例:7)ATRの変動が激しくなり、SL幅の変動も大きくなる。期間を長くすると(例:28)ATRが「過去の平均」を反映し、現在のボラティリティ環境への追従が遅れる。
バックテストで期間を最適化する場合、オーバーフィッティングに注意が必要だ。筆者が検証したところ、期間最適化の効果は倍数最適化ほど顕著ではなく、デフォルトの14から大きく外れると逆にパフォーマンスが安定しないケースが多かった。私は少なくとも3つ以上の通貨ペア・時間足でロバスト性を確かめてから採用するようにしている。
バックテスト最適化と実運用のギャップ
最適化されたパラメータは過去データに対して最良だったというだけであり、将来の相場での有効性を保証しない。特にATR倍数はボラティリティレジームが変化したとき(例:低ボラ相場から高ボラ相場への転換)に機能が劣化しやすい。
定期的な再最適化(例:四半期ごと)またはウォークフォワード分析による検証が実運用では必要だ。バックテスト期間を学習用・検証用に分割し、検証期間でも安定したパフォーマンスが出るパラメータだけを採用する。これが私の実運用での基準になっている。
よくある質問(FAQ)
Q: ATRのデフォルト期間14を変えるとどうなりますか?
A: 期間を短くするとATR値の変動が激しくなりSL幅が頻繁に変化します。長くするとATRが過去の平均値に近づき現在のボラティリティへの追従が遅れます。Wilder(1978年)が推奨した14を基準に、±7程度の範囲でロバスト性を確認したうえで調整するのが現実的です。
Q: ATR-SLはスキャルピングEAに向いていますか?
A: 向いていますが、設定に注意が必要です。短期足(M1〜M5)のATRは値が小さく、ブローカーのSYMBOL_TRADE_STOPS_LEVEL(最小SL距離)に引っかかりやすいです。前述のMathMax()によるSL下限チェックを必ず実装してください。また1.0x未満の倍数は65%超の誤発動率となるため、現実的な下限は1.0xです。
Q: iATR()とCopyBuffer()の使い方がわかりません
A: iATR()はハンドル(int型の整数)を返します。このハンドルを使ってCopyBuffer(ハンドル, バッファ番号, 開始バー, 取得本数, 配列名)でデータを取得します。ATRはバッファ番号0のみです。CopyBuffer()の戻り値が取得できた要素数なので、1未満の場合はデータ未取得として処理をスキップしてください。詳細は本稿のOnTick()実装コードを参照してください。
Q: ブローカーによってSLが設定できないことがありますか?
A: あります。SYMBOL_TRADE_STOPS_LEVEL(最小SL距離)を下回るSLは発注時にエラーが返ります。特にスキャルピング系のEAで発生しやすく、本稿のコードではMathMax()でATR-SLと最小SL距離を比較し大きい方を採用する実装で対処しています。ブローカーによっては最小SL距離が0に設定されているケースもありますが、その場合はコード内で最低10ポイントのフォールバック値を設けています。
Q: ATR-SLとトレーリングストップを同時に使えますか?
A: 使えます。一般的な設計はエントリー時にATR-SLを設定し、利益が一定幅(例:ATR×1.0倍)を超えたらトレーリングに切り替えるという方法です。本稿のTrailingStop()関数と組み合わせる場合、トレーリング倍数をエントリー時SL倍数以下に設定すると、エントリー直後にトレーリングが働いてSLが変動するという意図しない挙動を防げます。
Q: ATR期間・SL倍数・RR比率のパラメータ最適化にAIを活用できますか?
A: 活用できます。パラメータの組み合わせを検討してコードを修正するサイクルはAIアシスタントと対話しながら進めると効率的です。Claudeと会話しながらインジケータが作れるHedgrow FXでは、iATR()を使ったEAコードの生成・倍数最適化の設計・バックテスト結果の解釈まで、インタラクティブに対応できます。
免責事項
本稿に掲載されているコード・統計データ・パラメータ設定はすべて情報提供を目的としたものであり、特定の投資成果を保証するものではありません。FX取引は元本の全額を超える損失が生じる可能性のある高リスクな金融商品です。ATR-SLはリスク管理の一手法にすぎず、損失を完全に防ぐものではありません。実運用前には十分なバックテストとデモトレードによる検証を行い、ご自身の資金・リスク許容度に合わせた判断をしてください。本稿の内容に基づく取引により生じた損失について、筆者および当メディアは一切の責任を負いません。
Claudeと会話しながらインジケータが作れるHedgrow FXでは、MQL5コードの生成・ATR-SLパラメータの検討・バックテスト結果の解釈をAIとのチャットで進められる。
