FX EA にADXフィルターを実装する方法|MQL5コードと最適設定値を解説
最終更新: 2026年7月
トレンドフォロー系EAが最も苦手とするのが、レンジ相場での連続損失だ。移動平均クロスやDIクロスといったシグナルは、方向感のない横ばい相場でだましを連発し、資産曲線を静かに削っていく。この問題に対して1978年から使われてきた解決策が、ADX(Average Directional Index)によるトレンド強度フィルタリングである。
本稿で扱うのはMQL5での実装に絞る。iADX()とiADXWilder()の技術的な差異、閾値20・25・30の実証比較(quant-signals.comによる763バックテスト分析)、そしてリペイントを防ぐ確定バー判定まで——日本語で読める記事の多くが素通りしてきた部分だ。
免責事項: 本記事はMQL5実装技術の解説を目的としています。FX取引には元本割れのリスクがあります。バックテストの過去成績は将来の利益を保証するものではありません。必ずデモ口座で十分な検証を行ってください。
ADXとは何か|EAフィルターとして使う理由
ADXはJ. Welles Wilderが1978年の著書「New Concepts in Technical Trading Systems」で発表した指標だ。シンプルに言えば、「今の相場にどれくらい方向性があるか」をスコア化したもの。値が高いほどトレンドが強く、低いほどレンジ気味という解釈になる。
重要な点がひとつある。ADXは方向を示さない。上昇トレンドか下降トレンドかは、+DIと-DIの大小関係を参照する。ADX単体では「強い相場かどうか」しかわからない。EAフィルターとして使うとき、この性質を正確に理解していないと、「ADX>25だから強いトレンド」と判断しつつ逆張りエントリーするという矛盾したロジックになる。
ADXの計算式
+DM = 当日高値 - 前日高値(正値かつ -DM より大きい場合。それ以外は 0)
-DM = 前日安値 - 当日安値(正値かつ +DM より大きい場合。それ以外は 0)
TR = max(当日高値-当日安値, |当日高値-前日終値|, |当日安値-前日終値|)
TR14 = 前日TR14 - (前日TR14 / 14) + 当日TR ← Wilder平滑化
+DI14 = (+DM14の平滑値 / TR14) × 100
DX = |+DI14 - -DI14| / (+DI14 + -DI14) × 100
ADX = (前回ADX × 13 + 当日DX) / 14 ← 再帰的平滑化
DIとADXの両方に「Wilder平滑化(Wilder's Smoothed Moving Average)」が使われている。EMAとは異なり、乗数が 1/期間 に固定された平滑化方式で、収束が非常に遅い。この収束の遅さが後述する「150本問題」を生む。
EAフィルターとして使う理由
| ADX値 | 相場解釈 |
|---|---|
| 20未満 | レンジ相場(トレンドフォロー系は不利) |
| 20〜25 | グレーゾーン(個別に判断が必要) |
| 25以上 | 強いトレンド(フォロー系が有利) |
| 40以上 | 過熱状態(反転リスクが高まる) |
EAフィルターとしての実用的な意義は、「機械的にシグナルを間引く」ことにある。quant-signals.comが実施した763バックテスト(6商品を対象)によると、ADX>20フィルターを適用したBTCUSD日足では取引数が162件から30件へ、約81%削減された事例が報告されている。取引回数が減ること自体が目的ではないが、質の低いシグナルを削ぎ落とす効果は明確だ。
ただし、これは過去データに対する結果であり、将来の相場で同様の改善が再現される保証はない。
ADXと+DI/-DIの組み合わせによる方向フィルターの詳細な使い方はDMIとADXをEAフィルターに組み込む方法も合わせて参照してほしい。

MQL5でのADX実装:iADX()とiADXWilder()の使い分け
MQL5でADXを計算できる関数は2種類ある。iADX()しか知らない実装者が多いが、iADXWilder()との違いは実装上の意味を持つ——チャート表示との整合性を優先するか、バックテスト再現性を優先するかで使い分ける。
iADX()
int iADX(
string symbol, // 通貨ペア(NULL で現在のシンボル)
ENUM_TIMEFRAMES period, // 時間足(0 で現在の時間足)
int adx_period // ADX 計算期間
);
スムージングにEMAを使用する。MetaTrader 5の標準的なADXインジケーター表示と一致するため、視覚的なチャート確認と数値的なEA判定を整合させたい場合に適している。実務では最も広く使われる選択肢だ。
iADXWilder()
int iADXWilder(
string symbol,
ENUM_TIMEFRAMES period,
int adx_period
);
スムージングにSMMA(Wilder's Smoothed Moving Average)を使用する。Wilderの原著で示された計算式に忠実な実装で、バックテストの再現性が求められる研究目的や、他のプラットフォームとの数値比較を行う場面で有効だ。
| 関数 | スムージング手法 | 主な用途 |
|---|---|---|
iADX() | EMA | MetaTrader 標準表示との整合 |
iADXWilder() | SMMA(Wilder 原著式) | バックテスト再現性・学術的検証 |
筆者の実装経験では、ほとんどのトレンドフォロー系EAでは両者の差は実質的に小さく、フィルター判定のしきい値±1程度の違いしか生じない。ただし、厳密な研究目的にはiADXWilder()を選ぶべきだ。
バッファインデックスの参照
CopyBuffer()でインデックスに数値を直書きしている実装を見かけることがある。マジックナンバーのままにすると、3ヶ月後に自分でもどのバッファか判断できなくなる。公式定数名を使えばそれだけで済む。
// 非推奨(マジックナンバー)
CopyBuffer(handle, 0, 0, 3, adxMain);
// 推奨(公式定数名)
CopyBuffer(handle, MAIN_LINE, 0, 3, adxMain);
CopyBuffer(handle, PLUSDI_LINE, 0, 3, adxPlus);
CopyBuffer(handle, MINUSDI_LINE,0, 3, adxMinus);
| 定数名 | インデックス | 取得データ |
|---|---|---|
MAIN_LINE | 0 | ADX メインライン |
PLUSDI_LINE | 1 | +DI(プラス方向指数) |
MINUSDI_LINE | 2 | -DI(マイナス方向指数) |
ADXフィルターをOnTick()に組み込む完全実装コード
以下に、ADXフィルター付きのEA骨格を示す。シグナルロジック部分はコメントで示し、ADXフィルターの実装パターンに集中した構成にした。
//+------------------------------------------------------------------+
//| ADX Filter EA — 実装骨格 |
//+------------------------------------------------------------------+
#property copyright "hedgrow-media / 解説用サンプル"
#property version "1.00"
// ---- グローバル変数 ----
int g_adxHandle;
double g_adxMain[];
double g_adxPlus[];
double g_adxMinus[];
input int ADX_Period = 14; // ADX 計算期間
input double ADX_Threshold = 25.0; // ADX フィルター閾値
//+------------------------------------------------------------------+
int OnInit()
{
g_adxHandle = iADX(_Symbol, _Period, ADX_Period);
if(g_adxHandle == INVALID_HANDLE)
{
Print("iADX ハンドル取得失敗。EA を停止します。");
return(INIT_FAILED);
}
// バッファを時系列順に並べる
ArraySetAsSeries(g_adxMain, true);
ArraySetAsSeries(g_adxPlus, true);
ArraySetAsSeries(g_adxMinus, true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick()
{
// ---- 確定バー判定(リペイント防止)----
static datetime last_bar_time = 0;
datetime current_bar_time = (datetime)SeriesInfoInteger(
_Symbol, _Period, SERIES_LASTBAR_DATE);
if(current_bar_time == last_bar_time) return;
last_bar_time = current_bar_time;
// ---- ADX バッファのコピー ----
if(CopyBuffer(g_adxHandle, MAIN_LINE, 0, 3, g_adxMain) < 3) return;
if(CopyBuffer(g_adxHandle, PLUSDI_LINE, 0, 3, g_adxPlus) < 3) return;
if(CopyBuffer(g_adxHandle, MINUSDI_LINE, 0, 3, g_adxMinus) < 3) return;
// インデックス[1] = 確定した直前バーの値
double adxVal = NormalizeDouble(g_adxMain[1], 2);
double plusDI = NormalizeDouble(g_adxPlus[1], 2);
double minusDI = NormalizeDouble(g_adxMinus[1], 2);
// ---- ADX フィルター判定 ----
if(adxVal < ADX_Threshold)
{
// トレンドが弱いのでエントリー見送り
return;
}
// ---- 方向判定(+DI / -DI クロスまたは優位で決定)----
if(plusDI > minusDI)
{
// 上昇トレンド確認 → 買いシグナル処理をここに記述
}
else if(minusDI > plusDI)
{
// 下降トレンド確認 → 売りシグナル処理をここに記述
}
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(g_adxHandle);
}
確定バー判定のロジック解説
OnTick()は価格が動くたびに毎回呼ばれる。バー内でADX値が微妙に変化するため、未確定バーの値を参照するとシグナル判定がティックごとにブレる。SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE)は現在のバー([0]インデックス、形成中)の開始時刻を返す。前回の呼び出し時から値が変化した場合は新バーが開いたことを意味するため、そのタイミングでのみ後続処理を実行できる。
[1]を参照する理由も同じ発想だ。[0]は現在形成中のバーを指し、[1]は一本前の確定バーを指す。フィルター判定には必ず[1]以降の確定値を使う。
ADX閾値のチューニング:20・25・30の実証比較
閾値をどこに設定するかは、EAの取引頻度と選別精度のトレードオフだ。単純な理論では「高い閾値ほど強いトレンドのみに絞るから勝率が上がる」と思いがちだが、実際のデータを見るとやや複雑な様相を呈する。
763バックテスト分析(quant-signals.com)
quant-signals.comが6商品を対象に実施した763件のバックテスト分析では、ADX>20のDI Crossover戦略が以下の成績を示した。
| 商品 | 勝率 | PF | 最大DD |
|---|---|---|---|
| BTCUSD(日足) | 43.8% | 1.56 | 5.8% |
| XAUUSD(日足) | 43.6% | 1.54 | 5.0% |
出典: quant-signals.com「ADX Indicator Backtest」(2024年)
過去の実績は将来の成績を保証するものではありません。
注目すべきは、閾値を25や30に引き上げても、PFや勝率が単調に改善するわけではなかった点だ。同分析では日足においては閾値20が最優秀パラメータとなるケースが多く確認された。これは直感に反するが、閾値を高く設定することで取引機会が激減し、少数サンプルの統計的ノイズが支配的になるためと解釈できる。
fx-prog.comによるUSDJPY M15バックテスト
fx-prog.comが報告したUSDJPY 15分足の検証では、ADX=25フィルターを適用した条件で以下の結果が得られた。
| 指標 | 値 |
|---|---|
| PF(プロフィットファクター) | 1.41 |
| 勝率 | 46% |
| 総取引数 | 311(2024年8月〜2025年6月) |
出典: fx-prog.com(2025年)
過去の実績は将来の成績を保証するものではありません。
M15のような短期足では、ADX>20で取引数が多すぎてスプレッドコストに圧迫されるケースがある。短期足では閾値25前後が無難な出発点になるが、使用する通貨ペアと時間足によって最適値はまるで違う。他人のパラメータを検証なしに流用するのは、最初から負けを織り込んだようなものだ。
私が実際に使い分けている基準は以下だ:
- 日足・週足: ADX>20が有効なケースが多い(サンプル数確保のため)
- 4時間・1時間足: ADX>20〜25の範囲で商品ごとに検証
- 15分以下の短期足: ADX>25〜30が無駄なエントリーを減らしやすい
ADX25フィルターを実際のトレンドフォローEAに組み込んだ設計例はADX 25フィルター付きトレンドフォローEAの実装で確認できる。
Claudeと会話しながらインジケータが作れるHedgrow FXはこちら。ADXフィルターの閾値チューニングやバックテスト検証もAIとの対話で効率化できる。
ADX×移動平均・RSI・ボリンジャーバンドとの組み合わせ
ADXは単体で使うより、他のインジケーターとの組み合わせで威力を発揮する。方向性フィルター(ADX)と方向決定ロジック(MA・RSI等)を役割分担させる考え方だ。
ADX × 移動平均クロス
私が実装で最も多く使う組み合わせがこれだ。MAクロスでエントリーシグナルを生成し、ADXがトレンドの強度を確認するフィルターとして機能する。
// MA クロス + ADX フィルターの判定ロジック(概念実装)
bool isBullishCross = (maFast[1] > maSlow[1]) && (maFast[2] <= maSlow[2]);
bool isBearishCross = (maFast[1] < maSlow[1]) && (maFast[2] >= maSlow[2]);
bool isStrongTrend = (adxVal >= ADX_Threshold);
if(isStrongTrend && isBullishCross)
{
// 強いトレンド下でのMAゴールデンクロス → 買いエントリー
}
if(isStrongTrend && isBearishCross)
{
// 強いトレンド下でのMAデッドクロス → 売りエントリー
}
ADX × RSI
eodhd.comが公開したAAPL(Apple株)の13年分バックテストでは、ADX>35かつRSI境界50の組み合わせ戦略が総利益198%を記録し、ベンチマーク比+39%の超過リターンを示した事例がある(出典: eodhd.com「ADX and RSI Trading Strategy」)。株式データであり、FXへの直接適用はできないが、組み合わせの有効性を示す参考事例として挙げた。
過去の実績は将来の成績を保証するものではありません。
FXで同様のロジックを組む場合、ADX>25〜30でトレンド確認、RSIで買われ過ぎ・売られ過ぎのフィルターという役割分担が理にかなっている。ただし、強トレンド相場でRSIが買われ過ぎに達したまま上昇し続けるケースは頻発する。RSI境界を固定値で設定すると、この局面で機会を大量に取りこぼす。筆者自身、動的設定に切り替えたのはこの失敗を経験してからだ。
ADX × ボリンジャーバンド(スクイーズ後のブレイク判定)
ボリンジャーバンドのスクイーズ(収縮状態)はエネルギーの蓄積を示唆するとされる。ADXの上昇と組み合わせることで、スクイーズ解消後のブレイク方向を確認できる。
// BBスクイーズ + ADX 上昇の組み合わせ判定
double bandwidth = 100.0 * ((bb_upper[1] - bb_lower[1]) / bb_middle[1]);
bool isSqueeze = (bandwidth < 2.0); // バンド幅 2% 未満でスクイーズ判定
bool isADXRising = (g_adxMain[1] > g_adxMain[2]); // ADX が上昇中
bool isADXAbove = (g_adxMain[1] >= 20.0);
// スクイーズ解消 + ADX 上昇でエントリーフィルターを通過
if(!isSqueeze && isADXRising && isADXAbove)
{
// DIの方向で買い/売りを決定
if(plusDI > minusDI)
{
// 買いエントリーロジック
}
}
スクイーズの閾値(上記では2.0)は通貨ペアと時間足で大きく異なるため、固定値は使わず、ATR比やパーセンタイル計算で動的に設定する方が堅牢だ。この点は「コードで書けること」と「最適化に必要なこと」が大きく乖離する領域で、筆者が実装する際は必ず複数の通貨ペアで個別に検証している。
BBスクイーズの判定ロジックとバンド幅パラメーターの詳細な実装はボリンジャーバンドのスクイーズ・バンドウォークをEAに実装する方法でも詳しく取り上げている。
ADXフィルター実装の注意点(リペイント・過剰フィルタリング)
リペイント問題
MQL5でよく見落とされるのが、インデックス[0]の参照だ。[0]は現在形成中の未確定バーを指し、ティックのたびに値が変わる。バックテストでは確定後の値が使われるが、リアルタイムでは[0]を使うと「シグナルが出た→消えた→また出た」という不安定な挙動になる。
先のコードで示したSeriesInfoInteger()による確定バー判定と、[1]以降の参照を徹底することでリペイントを防げる。
Wilder平滑化の「150本問題」
ADXはバックテスト開始直後に使い物にならない数値を出力する。StockChartsのChartSchool資料によると、Wilder平滑化が安定するには約150本のバーが必要とされる。
これは見過ごされがちな落とし穴だ。たとえばバックテスト開始日を2020年1月1日に設定した場合、その後の数ヶ月はADX値が実態と大きく乖離している可能性がある。バックテスト設定では少なくとも200本分のウォームアップ期間を確保する。さらにEA内部でバーカウントチェックを入れれば、起動直後の誤シグナルを確実に防げる。
// ウォームアップ未完了時は処理をスキップ
int bars_available = Bars(_Symbol, _Period);
if(bars_available < ADX_Period * 15) // 目安として期間の15倍以上を要求
{
Print("バー数不足。ADX 値が安定していない可能性があります。");
return;
}
過剰フィルタリングの罠
フィルターを重ねるほどシグナルが減り、「ノイズが取れた」と感じやすい。しかし統計的には、サンプル数の減少によって検定力が落ち、バックテスト結果の信頼区間が拡大する。
ADX>25 + RSI条件 + BBスクイーズ条件を全部重ねた結果、年間取引回数が10件以下になったとき、そのバックテストの統計的有意性を評価するのは困難だ。N<30では中心極限定理の近似精度が著しく下がる。フィルターを積み上げるたびに取引回数を確認するのは、定量的なバックテスト設計の最低限だ。これを怠ると、「少ないサンプルのノイズで勝ったEA」を本番稼働させることになる。
カーブフィッティングの検出方法とアウトオブサンプルテストの具体的な設計手順はEAのカーブフィッティングを避けるバックテスト設計を参考にしてほしい。
まとめ:ADXフィルターで勝率を上げるためのチェックリスト
ADXフィルターは正しく実装すれば強力だ。ただ万能ではない。以下は私が毎回確認している実装チェックリストだ。
実装チェックリスト
-
iADX()またはiADXWilder()のどちらを使うか目的に応じて選択した -
OnInit()でハンドル取得が成功しているか確認(INVALID_HANDLEチェック) - バッファ参照に公式定数名(
MAIN_LINE等)を使用している - 確定バー判定を
SeriesInfoInteger()で実装し、[1]を参照している -
OnDeinit()でIndicatorRelease()を呼んでメモリを解放している
閾値設定チェックリスト
- 時間足と商品に応じた閾値を個別にバックテストで検証した
- バックテスト開始時に150本以上のウォームアップ期間を設けた
- 取引回数が統計的評価に十分なサンプル数(目安:50件以上)を確保している
- 閾値の過剰最適化(カーブフィッティング)をアウトオブサンプルテストで確認した
組み合わせ実装チェックリスト
- ADXの役割(強度フィルター)と方向判定(MA・RSI等)の役割を明確に分離した
- 複数フィルターを追加した後も十分な取引回数が確保されている
- フォワードテストまたはデモ取引でリアルタイムシグナルを確認した
Claudeと会話しながらインジケータが作れるHedgrow FXはこちら。MQL5コードの生成・デバッグ・パラメーター検討をAIとの対話で進められる。
よくある質問(FAQ)
Q: ADXの期間14はなぜ14なのか? A: J. Welles Wilderが1978年の著書で、2週間(14日間)の相場サイクルを基準として設定した経験則に由来する。数理的な最適性があるわけではなく、Wilderが当時の先物市場で経験的に有効と判断した期間だ。現代のFX市場でも広く使われているのは「デファクトスタンダード」としての慣性によるところが大きく、商品や時間足によっては期間を変更する価値がある。
Q: ADXフィルターを追加するとトレード回数はどう変わるか? A: quant-signals.comの763バックテスト分析によると、ADX>20フィルターで約81%のシグナルが除外された事例(BTCUSD日足:162件→30件)が報告されている。ただし、削減率はシグナルロジックや商品、時間足によって大きく異なる。FXの主要通貨ペアでは30〜60%程度の削減になるケースが多い印象だが、必ず自身のEAで実測してほしい。
Q: iADX()とiADXWilder()はどちらを使うべきか?
A: MetaTraderの視覚的なADX表示と数値を一致させたい場合はiADX()を選ぶ。Wilderの原著計算式を忠実に再現したい場合、あるいは他のプラットフォームとのバックテスト結果を比較検証したい場合はiADXWilder()が適している。実運用レベルでは、両者の差は閾値判定に実質的な影響を与えることはほとんどない。
Q: ADXが25以上でも機能しない場面はあるか? A: ある。特に重要指標発表直後や、中央銀行の介入が疑われる局面では、ADX値が短時間で急上昇し、「強いトレンド」と判定されながら実際にはノイズに近い動きをするケースがある。経済カレンダーとの組み合わせフィルターや、ニュース時間帯の取引停止ロジックをADXフィルターと併用することを検討してほしい。
Q: バックテストのADX値が実際のチャートと微妙に違うのはなぜか?
A: 最も多い原因は、バックテスト開始時のウォームアップ不足だ。Wilder平滑化の安定には約150本のバーが必要(StockCharts ChartSchool参照)で、開始直後のADX値は実態と大きくかけ離れる。また、iADX()とiADXWilder()の使用関数の違いや、ヒストリカルデータの品質(欠損バー・スプレッド)も数値差の要因になる。
免責事項
本記事はMQL5プログラミングに関する技術的情報の提供のみを目的としており、特定の金融商品への投資を推奨するものではありません。FX取引は元本割れのリスクを伴います。記事内のバックテスト結果はすべて過去データに基づくものであり、将来の利益・損失を保証するものではありません。実際のEA運用に際しては、デモ口座で十分な検証を行った上で、ご自身の判断と責任においてご利用ください。金融商品取引法上の投資助言には当たりません。
著者: Hedgrow Media 編集部(quantトレーダー / MQL5実装担当)
