FX自動売買 Python

FXのADXでおすすめの設定は?期間・基準値を5通貨1,000ケースで検証

本ページにはプロモーションが含まれています。

FXでADXを使う際、

  • 期間は14でいいの?
  • 基準値は20・25のどちらがいいの?
  • 通貨によって設定は変えた方がいいの?

と疑問に思う方も多いのではないでしょうか。

ADXでは、期間14・基準値20〜25あたりが一般的な設定としてよく使われます。

ただし、すべての通貨や売買方向でこの設定が最適とは限りません。

そこで本記事では、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、15分足を使ってADXの設定を検証しました。

検証するのは、

  • ADX期間10種類
  • ADX基準値10種類
  • LONG・SHORT

を組み合わせた合計1,000ケースです。

なお、出口条件による成績差をなくすため、すべての設定でTP50pips・SL30pipsに固定しています。

単純に合計pipsだけを見るのではなく、PF・最大ドローダウン・平均損益・取引回数なども確認しながら、成績が良かった設定を比較します。

この記事を読めば、一般的な期間14・基準値20〜25が本当に使いやすい設定なのか、通貨やLONG・SHORTによってどのような違いがあるのかが分かります。

先に結果を確認したい方は、「5通貨でADX100設定を検証」からご覧ください。

今回のバックテストでは、GMOコインFX APIから取得した15分足のCSVデータを使用しています。

ローソク足データの取得方法は、以下の記事で詳しく紹介しています

また、今回使用したバックテストスクリプトは以下です。

run_adx_backtest.py
"""5通貨・LONG/SHORTでADX期間と基準値の100設定を比較する。

+DIと-DIのクロスで方向を決め、ADXが基準値以上のときだけエントリーする。
出口条件は全設定共通のTP50・SL30に固定する。
"""

import csv
import time
from collections import deque
from datetime import date, datetime, timedelta
from pathlib import Path


# 使い方:
# 1. fetch_gmo_candles.pyで、5通貨の15分足を取得します。
# 2. GMO-APIフォルダで次のコマンドを実行します。
#    python backtest_research/average_directional_index/run_adx_backtest.py
# 3. 結果は、このファイルと同じフォルダにあるresultsへ保存されます。

# ADXの計算、シグナル判定、売買には15分足だけを使用します。
ENTRY_TIMEFRAME = "15min"
ENTRY_TIMEFRAME_DURATION = timedelta(minutes=15)

# 既存のローソク足CSVに合わせ、ASK価格を読み込みます。
# BID価格、スプレッド、手数料、スリッページは考慮しません。
PRICE_TYPE = "ASK"

# CSV内のtimestamp_jstの日付が、この開始日から終了日までの足を検証します。
# 開始日と終了日のローソク足も検証対象に含みます。
BACKTEST_START_DATE = date(2024, 1, 1)
BACKTEST_END_DATE = date(2026, 7, 31)

# 検証する5つの通貨ペアと、買い・売りの2方向です。
SYMBOLS = ("USD_JPY", "EUR_JPY", "GBP_JPY", "EUR_USD", "AUD_USD")
SIDES = ("LONG", "SHORT")

# 「ADX期間」は値動きを何本分使って平滑化するかを表します。
# 「ADX基準値」はトレンドが十分に強いと判断する最低値です。
# ADXは方向を表さないため、売買方向は+DIと-DIのクロスで決めます。
ADX_PERIODS = (5, 7, 9, 12, 14, 18, 21, 25, 28, 35)
ADX_THRESHOLDS = (10, 15, 20, 25, 30, 35, 40, 45, 50, 55)
# 最長期間の初回ADXには「2 × period - 1」本目までの値動きが必要です。
WARMUP_BARS = max(ADX_PERIODS) * 2

# ADX設定を公平に比較するため、出口条件はTP50・SL30の1組に固定します。
TP_SL_PAIRS = ((50, 30),)

# __file__を基準にするため、どのフォルダから実行しても入出力先は同じです。
SCRIPT_DIR = Path(__file__).resolve().parent
GMO_API_DIR = SCRIPT_DIR.parents[1]
DEFAULT_DATA_DIR = GMO_API_DIR / "data" / "candles"
DEFAULT_OUTPUT_DIR = SCRIPT_DIR / "results"

# 期間・基準値・TP/SLごとの集計結果へ保存する列と、その並び順です。
RESULT_COLUMNS = (
    "通貨ペア", "売買方向", "ADX期間", "ADX基準値", "TP(pips)", "SL(pips)",
    "取引回数", "勝ち取引数", "負け取引数", "勝率(%)", "合計損益(pips)",
    "平均損益(pips)", "プロフィットファクター", "最大ドローダウン(pips)",
    "平均保有時間(分)", "最終足決済数",
)


def load_candles(path):
    """検証前のウォームアップ足と検証期間内のOHLCを読み込む。"""
    # deque(maxlen=WARMUP_BARS)は、上限を超えると古い足を自動的に捨てます。
    # そのため、検証開始前の全履歴ではなくADX計算に必要な本数だけを保持します。
    warmup = deque(maxlen=WARMUP_BARS)
    backtest = []
    # utf-8-sigを使うと、Excel向けのBOM付きCSVも読み込めます。
    with path.open("r", encoding="utf-8-sig", newline="") as file:
        for row in csv.DictReader(file):
            timestamp = datetime.fromisoformat(row["timestamp_jst"])
            # 終了日より後の足は指標計算にも売買にも使用しません。
            if timestamp.date() > BACKTEST_END_DATE:
                continue
            # CSVの文字列を、日時と計算可能なfloatへ変換します。
            candle = {
                "open_time": timestamp,
                "close_time": timestamp + ENTRY_TIMEFRAME_DURATION,
                "open": float(row["open"]),
                "high": float(row["high"]),
                "low": float(row["low"]),
                "close": float(row["close"]),
            }
            if timestamp.date() < BACKTEST_START_DATE:
                # 検証開始前の足はADXを準備するためだけに使用します。
                warmup.append(candle)
            else:
                backtest.append(candle)
    return list(warmup) + backtest, len(warmup), len(backtest)


def calculate_adx(candles, period):
    """Wilder方式でADX、+DI、-DIを計算する。"""
    if period <= 0:
        raise ValueError("ADX期間は1以上にしてください")
    size = len(candles)
    # 必要本数が揃う前は計算できないため、結果をいったんNoneで埋めます。
    adx = [None] * size
    plus_di = [None] * size
    minus_di = [None] * size
    if size <= period:
        return adx, plus_di, minus_di

    # TRは窓開けを含む実質的な値幅です。
    # +DMは上方向の値動き、-DMは下方向の値動きを表します。
    true_ranges = [0.0] * size
    plus_dm = [0.0] * size
    minus_dm = [0.0] * size
    for index in range(1, size):
        current = candles[index]
        previous = candles[index - 1]
        # TRは次の3つのうち最大の値です。
        # 1. 現在足の高値-安値
        # 2. 現在足の高値-前足終値
        # 3. 現在足の安値-前足終値
        true_ranges[index] = max(
            current["high"] - current["low"],
            abs(current["high"] - previous["close"]),
            abs(current["low"] - previous["close"]),
        )
        upward = current["high"] - previous["high"]
        downward = previous["low"] - current["low"]
        # 上昇幅と下落幅を比べ、大きい方向だけをDMとして採用します。
        # 両方を同時に採用しないことがWilder方式のポイントです。
        plus_dm[index] = upward if upward > downward and upward > 0 else 0.0
        minus_dm[index] = downward if downward > upward and downward > 0 else 0.0

    # 最初の平滑値は、直近period本の単純な合計から開始します。
    smoothed_tr = sum(true_ranges[1:period + 1])
    smoothed_plus = sum(plus_dm[1:period + 1])
    smoothed_minus = sum(minus_dm[1:period + 1])
    dx = [None] * size
    for index in range(period, size):
        if index > period:
            # Wilderの平滑化:
            # 前回値-前回値÷期間+今回値
            smoothed_tr = smoothed_tr - smoothed_tr / period + true_ranges[index]
            smoothed_plus = smoothed_plus - smoothed_plus / period + plus_dm[index]
            smoothed_minus = smoothed_minus - smoothed_minus / period + minus_dm[index]
        if smoothed_tr == 0:
            plus_di[index] = minus_di[index] = 0.0
        else:
            # +DIと-DIは、それぞれの方向の値動きが全体の値幅に占める割合です。
            plus_di[index] = 100.0 * smoothed_plus / smoothed_tr
            minus_di[index] = 100.0 * smoothed_minus / smoothed_tr
        denominator = plus_di[index] + minus_di[index]
        # DXは+DIと-DIの離れ具合を0~100で表します。
        dx[index] = (
            100.0 * abs(plus_di[index] - minus_di[index]) / denominator
            if denominator else 0.0
        )

    # 最初のADXは、計算済みのDXをperiod本平均して作ります。
    # そのため、ADX14の最初の値はindex=27に入ります。
    first_adx_index = period * 2 - 1
    if first_adx_index >= size:
        return adx, plus_di, minus_di
    adx[first_adx_index] = sum(dx[period:first_adx_index + 1]) / period
    for index in range(first_adx_index + 1, size):
        # 2つ目以降のADXもWilder方式でなめらかに更新します。
        adx[index] = (adx[index - 1] * (period - 1) + dx[index]) / period
    return adx, plus_di, minus_di


def make_trade(position, candle, exit_price, reason, side, pip_size):
    """決済した1取引の保有時間、pips損益、決済理由を作る。"""
    # LONGは値上がり、SHORTは値下がりしたときに損益をプラスにします。
    direction = 1.0 if side == "LONG" else -1.0
    return {
        # 日時差なので、土日やデータ欠損をまたぐ時間も含みます。
        "保有時間(分)": (
            candle["close_time"] - position["time"]
        ).total_seconds() / 60,
        # 円通貨は1pips=0.01円、それ以外は1pips=0.0001で換算します。
        "損益(pips)": direction * (exit_price - position["price"]) / pip_size,
        "決済理由": reason,
    }


def collect_trades(candles, adx, plus_di, minus_di, side, threshold, tp, sl,
                   pip_size, entry_start_index):
    """DIクロスかつADX基準値以上で入り、固定TP/SLで決済する。"""
    # positionがNoneならポジションを持っていません。
    # pending_entryは、現在足で確定した注文を次足始値で実行する予約です。
    position = None
    pending_entry = False
    trades = []
    # 前足と比較するため、2本目(index=1)から順番に処理します。
    for index in range(1, len(candles)):
        # 前の15分足でシグナルが確定していれば、現在足の始値で入ります。
        if position is None and pending_entry and index >= entry_start_index:
            # シグナル足の終値ではなく次足始値を使い、未来参照を防ぎます。
            position = {
                "time": candles[index]["open_time"],
                "price": candles[index]["open"],
            }
        # 予約は一度だけ有効です。約定の有無にかかわらず毎足リセットします。
        pending_entry = False

        # ポジション保有中は、現在足の高値・安値でTP/SL到達を確認します。
        if position is not None:
            tp_distance = tp * pip_size
            sl_distance = sl * pip_size
            if side == "LONG":
                # LONGは上昇でTP、下落でSLへ到達します。
                tp_price = position["price"] + tp_distance
                sl_price = position["price"] - sl_distance
                tp_hit = candles[index]["high"] >= tp_price
                sl_hit = candles[index]["low"] <= sl_price
            else:
                # SHORTは下落でTP、上昇でSLへ到達します。
                tp_price = position["price"] - tp_distance
                sl_price = position["price"] + sl_distance
                tp_hit = candles[index]["low"] <= tp_price
                sl_hit = candles[index]["high"] >= sl_price
            # 15分足内の到達順を判別できない場合は、保守的にSLを優先します。
            if sl_hit:
                trades.append(make_trade(
                    position, candles[index], sl_price, "SL", side, pip_size,
                ))
                position = None
            elif tp_hit:
                trades.append(make_trade(
                    position, candles[index], tp_price, "TP", side, pip_size,
                ))
                position = None

        # シグナル判定には現在足と前足のDI、および現在足のADXが必要です。
        values = (
            adx[index], plus_di[index], minus_di[index],
            plus_di[index - 1], minus_di[index - 1],
        )
        # ポジション保有中や、指標をまだ計算できない位置では判定しません。
        if position is not None or any(value is None for value in values):
            continue
        if side == "LONG":
            # LONGは+DIが-DIを下から上へ抜けた瞬間を探します。
            crossed = (
                plus_di[index - 1] <= minus_di[index - 1]
                and plus_di[index] > minus_di[index]
            )
        else:
            # SHORTは-DIが+DIを下から上へ抜けた瞬間を探します。
            crossed = (
                minus_di[index - 1] <= plus_di[index - 1]
                and minus_di[index] > plus_di[index]
            )
        # ADXが基準値以上なら、十分なトレンド強度があるものと判断します。
        if crossed and adx[index] >= threshold:
            pending_entry = True

    # データ終了時にポジションが残っていれば、最終足終値で強制決済します。
    if position is not None:
        trades.append(make_trade(
            position, candles[-1], candles[-1]["close"], "データ終了", side, pip_size,
        ))
    return trades


def summarize(symbol, side, period, threshold, tp, sl, trades):
    """取引ごとのpips損益から、1設定の全期間成績をまとめる。"""
    profits = [trade["損益(pips)"] for trade in trades]
    # 0pipsは勝ちにも負けにも含めませんが、取引回数には含めます。
    wins = [value for value in profits if value > 0]
    losses = [value for value in profits if value < 0]
    # 累積損益の過去最高値から、最大で何pips下落したかを計算します。
    equity = peak = max_drawdown = 0.0
    for profit in profits:
        equity += profit
        peak = max(peak, equity)
        max_drawdown = max(max_drawdown, peak - equity)
    count = len(profits)
    # PF(プロフィットファクター)= 総利益 ÷ 総損失です。
    gross_loss = abs(sum(losses))
    durations = [trade["保有時間(分)"] for trade in trades]
    return {
        "通貨ペア": symbol, "売買方向": side, "ADX期間": period,
        "ADX基準値": threshold, "TP(pips)": tp, "SL(pips)": sl,
        "取引回数": count, "勝ち取引数": len(wins), "負け取引数": len(losses),
        "勝率(%)": round(len(wins) / count * 100, 2) if count else 0.0,
        "合計損益(pips)": round(sum(profits), 2),
        "平均損益(pips)": round(sum(profits) / count, 2) if count else 0.0,
        "プロフィットファクター": (
            round(sum(wins) / gross_loss, 4) if gross_loss else ""
        ),
        "最大ドローダウン(pips)": round(max_drawdown, 2),
        "平均保有時間(分)": (
            round(sum(durations) / count, 2) if count else 0.0
        ),
        "最終足決済数": sum(trade["決済理由"] == "データ終了" for trade in trades),
    }


def write_csv(path, rows, fieldnames=RESULT_COLUMNS):
    """集計結果をExcelでも開きやすいUTF-8 BOM付きCSVへ保存する。"""
    # resultsや通貨・方向別フォルダがなければ自動的に作成します。
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8-sig", newline="") as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


def run_experiment(candles, symbol, side, entry_start_index, output_dir):
    """1通貨・1方向でADX期間×基準値の100設定を検証する。"""
    indicators = {period: calculate_adx(candles, period) for period in ADX_PERIODS}
    # USDJPY・EURJPY・GBPJPYは1pips=0.01円、それ以外は0.0001です。
    pip_size = 0.01 if symbol.endswith("_JPY") else 0.0001
    rows = []
    tp, sl = TP_SL_PAIRS[0]
    for period, (adx, plus_di, minus_di) in indicators.items():
        for threshold in ADX_THRESHOLDS:
            trades = collect_trades(
                candles, adx, plus_di, minus_di, side, threshold, tp, sl,
                pip_size, entry_start_index,
            )
            rows.append(summarize(
                symbol, side, period, threshold, tp, sl, trades,
            ))
    # 全件CSVとは別に、通貨・売買方向ごとの100設定も保存します。
    write_csv(output_dir / symbol / side / "result.csv", rows)
    return rows


def main():
    """5通貨・2方向・ADX100設定の全1,000ケースを実行する。"""
    # スクリプト全体の実行時間を測定します。
    started = time.perf_counter()
    if BACKTEST_START_DATE > BACKTEST_END_DATE:
        raise SystemExit("検証開始日は検証終了日以前に設定してください")
    # 設定数から実際のケース数を計算するため、候補を変えても表示が追従します。
    total_cases = (
        len(SYMBOLS) * len(SIDES) * len(ADX_PERIODS) * len(ADX_THRESHOLDS)
    )
    print(
        f"5通貨 × 2方向 × ADX期間{len(ADX_PERIODS)}種類 × "
        f"基準値{len(ADX_THRESHOLDS)}種類 = {total_cases}ケース"
    )
    print(f"検証期間: {BACKTEST_START_DATE} ~ {BACKTEST_END_DATE}")

    # 同じ通貨のCSVをLONGとSHORTで2回読まないよう、先に1回だけ読み込みます。
    candles_by_symbol = {}
    entry_indexes = {}
    for symbol in SYMBOLS:
        path = DEFAULT_DATA_DIR / symbol / f"{ENTRY_TIMEFRAME}_{PRICE_TYPE}.csv"
        if not path.is_file():
            raise SystemExit(f"入力CSVが見つかりません: {path}")
        candles, warmup_count, backtest_count = load_candles(path)
        if backtest_count == 0:
            raise SystemExit(f"{symbol}の検証期間内データがありません")
        if warmup_count >= WARMUP_BARS:
            # 検証開始前の最後の足で確定したシグナルは、検証期間の最初の足から
            # エントリーできます。
            entry_index = warmup_count
            warmup_description = f"検証開始前{warmup_count}本"
        else:
            # CSVに検証開始前の足がない場合は、検証期間内の先頭70本を全設定で
            # 共通のウォームアップにします。短い期間だけ先に取引しないためです。
            if len(candles) <= WARMUP_BARS:
                raise SystemExit(f"{symbol}のデータが不足しています")
            entry_index = WARMUP_BARS
            warmup_description = f"CSV先頭{WARMUP_BARS}本"
            print(f"[WARN] {symbol}: {warmup_description}をウォームアップに使用")
        candles_by_symbol[symbol] = candles
        entry_indexes[symbol] = entry_index
        print(f"[LOAD] {symbol}: 検証{backtest_count}本 / ウォームアップ: {warmup_description}")

    # 通貨・方向別に検証しながら、全結果もall_rowsへ集めます。
    all_rows = []
    cases_per_experiment = len(ADX_PERIODS) * len(ADX_THRESHOLDS)
    for symbol in SYMBOLS:
        for side in SIDES:
            print(f"[RUN] {symbol} {side}: {cases_per_experiment}ケース")
            all_rows.extend(run_experiment(
                candles_by_symbol[symbol], symbol, side, entry_indexes[symbol],
                DEFAULT_OUTPUT_DIR,
            ))
    # 通貨・方向別CSVに加え、全1,000ケースをまとめたCSVも保存します。
    write_csv(DEFAULT_OUTPUT_DIR / "all_results.csv", all_rows)
    print(f"完了: {DEFAULT_OUTPUT_DIR}")
    print(f"実行時間: {time.perf_counter() - started:.2f}秒")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

同じようにADXの期間・基準値を検証してみたい方は、参考にしてみてください。

FXのADXとは

ADX(Average Directional Index)は、相場のトレンドの強さを確認するためのテクニカル指標です。

ADXの数値が高いほどトレンドが強く、低いほど方向感が弱い相場と考えます。

ただし、ADXだけでは上昇・下降の方向までは分かりません。そのため、一般的には「+DI」「-DI」とあわせて確認します。

ADXで分かること

ADXで分かるのは、主にトレンドの強さです。

ADXは0~100の範囲で推移し、数値が高くなるほどトレンドが強く、低くなるほどトレンドが弱いと考えます。

一般的には、次のようなイメージで確認します。

ADXの値相場の状態の目安
0~20トレンドが弱い
20~25トレンドが出始めている
25~30トレンドが比較的強い
30以上強いトレンドが発生している可能性

たとえばADXが15前後で推移している場合は、明確な方向感がなく、レンジ相場になっている可能性があります。

一方、ADXが25や30を超えて上昇している場合は、相場がどちらか一方向へ強く動いている可能性があります。

ここで注意したいのが、ADXの数値が高い=価格が上昇している、という意味ではないことです。

価格が大きく下落している場合でも、下降トレンドが強ければADXは高くなります。

そのため「ADX=トレンドの強さを見る指標」と覚えておくと分かりやすいでしょう。

+DI・-DIとは

+DI・-DIは、相場の方向を確認するための指標です。

基本的には、

  • +DIが-DIより上:上方向の力が強い
  • -DIが+DIより上:下方向の力が強い

と判断します。

また、+DIと-DIが交差するタイミングは、相場の方向が切り替わるサインとして使われます。

たとえば、+DIが-DIを下から上へ抜けた場合は上方向が優勢になったと考え、反対に-DIが+DIを上抜けた場合は下方向が優勢になったと考えます。

今回の検証でも、この+DIと-DIのクロスをLONG・SHORTのエントリー条件として使用しています。

FXのADXでよく使われる設定

ADXでは、主に期間基準値を設定して使用します。

  • 期間:ADXを計算するときにどれくらいの値動きを反映するかを決める値
    • 一般的には14がよく使われる
    • 期間を短くすると直近の値動きに反応しやすく、長くすると動きがなめらかになる
  • 基準値:「ADXがどのくらいの数値になったらトレンドが強いと判断するか」の目安
    • 一般的には20〜25あたりがよく使わる
    • 基準値を高くするほど強いトレンドに絞りやすくなる

ただし、期間14・基準値20〜25はあくまで一般的な目安であり、通貨ペアや相場状況によって適した設定は異なります。

今回のADX検証条件

今回の検証では、ADXの期間と基準値を変えたときに成績がどのように変わるのかを確認します。

対象は5通貨の15分足で、LONG・SHORTそれぞれを検証します。

また、ADX設定をできるだけ公平に比較するため、利益確定と損切りはすべて同じ条件です。

検証する5通貨と期間

今回検証する通貨ペアは、以下の5つです。

  • USDJPY
  • EURJPY
  • GBPJPY
  • EURUSD
  • AUDUSD

検証期間は、2024年1月1日〜2026年7月31日です。

すべて15分足を使用し、LONG・SHORTをそれぞれ検証します。

また、価格データにはASKを使用しています。スプレッド・手数料・スリッページなどは考慮していません。

検証するADX期間10種類・基準値10種類

ADX期間は、以下の10種類を検証します。

  • 5・7・9・12・14・18・21・25・28・35

ADXの基準値は、以下の10種類です。

  • 10・15・20・25・30・35・40・45・50・55

そのため、1通貨・1方向あたり「ADX期間10種類 × 基準値10種類 = 100設定」を検証します。

今回は5通貨のLONG・SHORTをそれぞれ検証するため、合計では「5通貨 × 2方向 × 100設定 = 1,000ケース」のバックテストを行います。

LONG・SHORTのエントリー条件

今回の検証では、+DIと-DIのクロスとADXの基準値を組み合わせてエントリーを判断します。

LONGのエントリー条件

  • +DIが-DIを下から上へ抜ける
  • ADXが設定した基準値以上

SHORTのエントリー条件

  • -DIが+DIを下から上へ抜ける
  • ADXが設定した基準値以上

条件を満たした足ですぐにエントリーするのではなく、シグナル確定後の次の15分足始値でエントリーします。

TP50・SL30に固定して検証

今回の目的は、ADXの期間・基準値による成績の違いを比較することです。

そのため、出口条件はすべての設定で共通とし、

  • TP:50pips
  • SL:30pips

に固定します。

また、1本の15分足の中でTPとSLの両方に到達した場合は、どちらに先に到達したか判断できないため、SLを優先して決済します。

バックテスト結果で確認する指標

各ADX設定について、主に以下の結果を確認します。

  • 取引回数
  • 勝率
  • 合計損益(pips)
  • 平均損益(pips)
  • プロフィットファクター(PF)
  • 最大ドローダウン(pips)
  • 平均保有時間

単純に合計pipsだけを見るのではなく、PFや最大ドローダウン、取引回数なども確認しながら、成績の良かったADX設定を比較します。

5通貨でADX100設定を検証

ここからは、5通貨それぞれについて、ADX100設定のバックテスト結果をLONG・SHORT別に確認します

今回は、

  • 取引回数100回以上
  • 合計pipsがプラス
  • PF1.05以上

を満たした設定を対象とします。

そのうえでPFの高い順に並べ、合計pipsや最大ドローダウンもあわせて確認します。

なお、今回の結果にはスプレッド・手数料・スリッページなどの取引コストは含めていません

また、基準を満たす設定が5つ未満の場合は、無理にTOP5を作らず、基準を満たした設定のみ掲載します。

USDJPYで成績が良かったADX設定

USDJPY LONG:TOP5

順位ADX期間基準値取引回数合計pipsPF最大DD
12115669+2,4101.2070790
21815791+2,6701.19311,010
32510796+2,6001.1864970
42110873+2,8501.1863970
53510607+1,7901.16711,030

USDJPY LONGの1位は、ADX期間21・基準値15です。

669回の取引で+2,410pips、PF1.2070、最大DD790pipsとなりました。

上位5設定を見ると、基準値はすべて10〜15です。

今回の検証では、USDJPY LONGはADXの基準値を比較的低めにした設定がPF上位に入りました。

USDJPY SHORT:基準を満たした1設定

順位ADX期間基準値取引回数合計pipsPF最大DD
12820168+1,0401.3768280

USDJPY SHORTで基準を満たしたのは、ADX期間28・基準値20の1設定だけでした。

168回の取引で+1,040pips、PF1.3768と数値自体は良好ですが、条件を満たした設定が1つしかありません。

そのため、今回の検証ではSHORTよりもLONGの方が、プラスとなるADX設定の選択肢が多い結果となりました。

EURJPYで成績が良かったADX設定

EURJPY LONG:TOP5

順位ADX期間基準値取引回数合計pipsPF最大DD
13520103+5101.2931560
2940133+6501.2889400
3745113+4501.2308330
4535915+2,9501.1838870
5550186+5801.1774700

EURJPY LONGのPF1位は、ADX期間35・基準値20です。

103回の取引で+510pips、PF1.2931となりました。

一方、4位の期間5・基準値35では915回の取引で+2,950pipsとなっており、PFだけでなく取引回数や合計pipsまで見ると印象が変わります。

そのため、PF1位だけで最適な設定を判断するのではなく、取引回数や利益とのバランスも確認する必要があります。

EURJPY SHORT:基準を満たした2設定

順位ADX期間基準値取引回数合計pipsPF最大DD
1550233+5301.1271690
2745151+2701.0989340

EURJPY SHORTで基準を満たしたのは2設定です。

1位はADX期間5・基準値50で、233回の取引で+530pips、PF1.1271となりました。

LONGでは基準を満たす設定が多かった一方、SHORTでは2設定のみとなっており、今回の条件ではEURJPYはLONG側の方が良い結果となっています。

GBPJPYで成績が良かったADX設定

GBPJPY LONG:TOP5

順位ADX期間基準値取引回数合計pipsPF最大DD
15351,062+2,8601.15181,280
2730904+2,3201.14431,310
33515315+7901.1408790
45151,918+4,7001.13741,620
55301,470+3,5801.13651,300

GBPJPY LONGのPF1位は、ADX期間5・基準値35です。

1,062回の取引で+2,860pips、PF1.1518となりました。

また、上位5設定のうち3設定がADX期間5です。

特に期間5・基準値15では、PFは4位ですが1,918回の取引で+4,700pipsとなっており、合計pipsは非常に大きい結果となりました。

GBPJPY LONGでは、短めのADX期間が上位に入りやすい傾向が見られます。

GBPJPY SHORT:基準を満たした1設定

順位ADX期間基準値取引回数合計pipsPF最大DD
12125172+2001.0635500

GBPJPY SHORTで基準を満たしたのは、ADX期間21・基準値25の1設定だけでした。

172回の取引で+200pips、PF1.0635となっています。

LONGでは多くの設定が基準を満たしたのに対し、SHORTでは1設定のみです。

今回の検証期間・ルールでは、GBPJPYもSHORTよりLONGの方が良い結果となりました。

EURUSDで成績が良かったADX設定

EURUSD LONG:基準を満たした1設定

順位ADX期間基準値取引回数合計pipsPF最大DD
1545271+3501.0707530

EURUSD LONGで基準を満たしたのは、ADX期間5・基準値45の1設定だけでした。

271回の取引で+350pips、PF1.0707となっています。

PF・合計pipsともに大きくはなく、今回の条件ではEURUSD LONGとADXの組み合わせはあまり良い結果ではありませんでした。

EURUSD SHORT:TOP5

順位ADX期間基準値取引回数合計pipsPF最大DD
11430155+7901.3027340
2550177+7701.2541240
3740229+9701.2468260
4935243+8701.2057390
5745115+3901.1940270

EURUSD SHORTの1位は、ADX期間14・基準値30です。

155回の取引で+790pips、PF1.3027、最大DD340pipsとなりました。

また、上位には基準値30〜50の設定が並んでいます。

EURUSDはLONGでは基準を満たした設定が1つだけだった一方、SHORTでは複数の設定が良い成績となりました。

今回の検証では、EURUSDはLONGよりSHORTの方がADXとの相性が良い結果となっています。

AUDUSDで成績が良かったADX設定

AUDUSD LONG:基準を満たした3設定

順位ADX期間基準値取引回数合計pipsPF最大DD
1745111+2701.1364400
2530463+5471.0647650
31425282+2601.0501790

AUDUSD LONGで基準を満たしたのは3設定です。

PF1位はADX期間7・基準値45で、111回の取引で+270pips、PF1.1364となりました。

一方、期間5・基準値30では463回の取引で+547pipsと、1位より取引回数・合計pipsが大きくなっています。

ただし、全体として見るとUSDJPY・EURJPY・GBPJPYのLONGほど強い結果ではありません。

AUDUSD SHORT:基準を満たした1設定

順位ADX期間基準値取引回数合計pipsPF最大DD
13515218+206.11.0516480

AUDUSD SHORTで基準を満たしたのは、ADX期間35・基準値15の1設定だけでした。

218回の取引で+206.1pips、PF1.0516となっています。

PFは基準の1.05をわずかに上回る程度で、合計pipsも大きくありません。

今回の検証では、AUDUSDはLONG・SHORTともに基準を満たす設定が少なく、他の通貨ペアと比べてADXによる優位性は小さい結果となりました。

5通貨のADX検証結果を比較

5通貨の結果を比較すると、すべての通貨で共通する最適なADX設定はありませんでした。

今回は、

  • 取引回数100回以上
  • 合計pipsがプラス
  • PF1.05以上

を満たした設定の中から、各通貨・売買方向でPFが最も高かった設定を比較します。

通貨売買方向ADX期間基準値PF
USDJPYLONG21151.2070
USDJPYSHORT28201.3768
EURJPYLONG35201.2931
EURJPYSHORT5501.1271
GBPJPYLONG5351.1518
GBPJPYSHORT21251.0635
EURUSDLONG5451.0707
EURUSDSHORT14301.3027
AUDUSDLONG7451.1364
AUDUSDSHORT35151.0516

今回特に目立ったのは、通貨ペアだけでなくLONG・SHORTによっても成績の良いADX期間・基準値が大きく異なったことです。

たとえばUSDJPYでは、LONGが期間21・基準値15だったのに対し、SHORTでは期間28・基準値20となりました。

EURJPYではさらに差が大きく、LONGは期間35・基準値20、SHORTは期間5・基準値50です。

一方、EURUSDではLONGのPFが1.0707にとどまったのに対し、SHORTでは期間14・基準値30でPF1.3027となり、売買方向によって大きな差が出ました。

今回最もPFが高かったのは、USDJPY SHORTの期間28・基準値20でPF1.3768です。

また、一般的によく使われる期間14・基準値20〜25が、すべての通貨で上位になったわけではありません。

今回の1,000ケースの検証結果から、ADXは定番の設定だけを使用するのではなく、通貨ペアやLONG・SHORTごとに期間・基準値を分けて検証することが重要だと分かります。

ADXをFXで使う際の注意点

ADXはトレンドの強さを確認するのに便利な指標ですが、ADXだけで売買を判断するのはおすすめできません。

また、通貨ペアや売買方向によって適した設定が異なる点や、バックテストと実運用では条件が異なる点にも注意が必要です。

ADXだけでエントリーを判断しない

ADXで分かるのは、トレンドの強さです。

ADXが高くても、それだけでは上昇トレンドなのか下降トレンドなのかは判断できません。

また、すでにトレンドが大きく進んだあとにADXが高くなっている場合もあります。

そのため、ADXは単独のエントリーシグナルとして使うのではなく、+DI・-DIなどと組み合わせて方向やタイミングを確認することが重要です。

通貨ペアや売買方向によって適した設定が異なる

今回の検証では、通貨ペアやLONG・SHORTによって成績の良いADX期間・基準値が異なりました。

そのため、期間14・基準値20〜25などの一般的な設定を、すべての通貨ペアにそのまま使えばよいとは限りません

ADXを使用する場合は、取引する通貨ペアや売買方向ごとに設定を検証することが重要です。

バックテスト結果がそのまま実運用の成績になるわけではない

バックテストで良い成績だった設定が、実際のFX取引でも同じ成績になるとは限りません。

今回の検証では、ADX設定を比較しやすくするため、スプレッド・手数料・スリッページなどの取引コストは考慮していません

特に、1取引あたりの平均利益が小さい設定や取引回数が多い設定は、取引コストを加味すると成績が悪化する可能性があります。

また、過去の相場で機能した設定が、今後も同じように機能するとは限らないため、バックテスト結果はあくまで設定を比較するための参考として考えましょう。

FXのADXに関するよくある質問

ここでは、本文で詳しく触れていないADXに関する疑問をまとめます。

ADXとRSIの違いは?

ADXとRSIでは、確認する内容が異なります。

指標主に確認するもの
ADXトレンドの強さ
RSI買われすぎ・売られすぎ

ADXは、相場にどの程度強いトレンドが発生しているかを確認する指標です。

一方、RSIは価格の上昇・下落の勢いから、買われすぎ・売られすぎを判断する際に使われます。

そのため、ADXはトレンドの強さ、RSIは相場の過熱感を見る指標と考えると分かりやすいでしょう。

ADXとMACDの違いは?

ADXとMACDも役割が異なります。

ADXはトレンドの強さを見る指標ですが、MACDは移動平均線をもとに、トレンドの方向や変化のタイミングを確認する際に使われます。

たとえば、

  • MACDで上昇方向を確認する
  • ADXでトレンドの強さを確認する

といった形で使い分けることができます。

ADXと相性の良いテクニカル指標は?

ADXはトレンドの強さを確認する指標なので、方向やエントリータイミングを判断できる指標と組み合わせやすいです。

代表的なものとして、

  • +DI・-DI
  • 移動平均線
  • MACD
  • RSI

などがあります。

たとえば、移動平均線で上昇トレンドを確認し、さらにADXが一定以上の場面だけをエントリー候補にするといった使い方ができます。

ADXはどの時間足で使える?

ADXは、15分足・1時間足・4時間足・日足など、さまざまな時間足で使用できます

ただし、時間足が変わるとADXの動きやシグナルの回数も変わるため、同じ設定がそのまま機能するとは限りません。

短い時間足ではシグナルが増えやすく、長い時間足では大きなトレンドを確認しやすくなる傾向があります。

今回の検証では15分足を使用していますが、他の時間足で利用する場合は、その時間足に合わせて期間や基準値を確認するのがおすすめです。

まとめ

今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、ADX期間10種類・基準値10種類・LONG/SHORTを組み合わせた1,000ケースを検証しました。

今回の結果から分かった主なポイントは、以下の通りです。

  • 一般的な期間14・基準値20〜25がすべての通貨で最適とは限らない
  • 通貨ペアによって成績の良いADX期間・基準値が大きく異なる
  • 同じ通貨でもLONGとSHORTで適した設定が異なる
  • USDJPY・EURJPY・GBPJPYではLONG側に良い設定が多く見つかった
  • EURUSDではSHORT側に比較的良い設定が見つかった
  • AUDUSDではLONG・SHORTともに基準を満たす設定が少なかった
  • ADXは通貨ペア・売買方向ごとに設定を分けて検証することが重要

今回、取引回数100回以上・合計pipsがプラス・PF1.05以上という条件を満たした設定の中で、最も高いPFとなったのはUSDJPY SHORTのADX期間28・基準値20でPF1.3768でした。

一方で、今回のバックテストではADX設定を比較することを目的としているため、TP50・SL30に固定し、スプレッド・手数料・スリッページなどの取引コストは考慮していません

また、2024年1月1日〜2026年7月31日の同一期間で設定を比較した結果であり、今回成績が良かった設定が今後も同じように機能するとは限りません。

実際にADXをトレードへ取り入れる場合は、今回の結果をそのまま使用するのではなく、取引コストや直近の相場環境を含めて再検証したうえで判断してください。

-FX自動売買, Python