FX自動売買 Python

FXのATRでおすすめの設定は?5通貨で期間・基準値を徹底検証

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

FXでATRを使う際、

  • 期間は14でいいの?
  • ATRはどのくらい高ければエントリーしていいの?
  • 通貨によって設定は変えた方がいいの?

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

ATRでは、期間14が一般的な設定としてよく使われます。

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

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

検証するのは、

  • ATR期間6種類
  • ATRフィルター3種類
  • TP・SL10パターン
  • LONG・SHORT

を組み合わせた合計2,400ケースです。

ATRフィルターでは、現在のATRを直前100本のATRと比較し、低ボラ30%除外・低ボラ50%除外・高ボラ30%のみの3条件を検証します。

また、ATRフィルターを使用しない条件も比較用として確認します。

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

この記事を読めば、一般的なATR14が本当に使いやすい設定なのか、通貨ペアやLONG・SHORTによってどのような違いがあるのかが分かります。

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

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

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

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

run_atr_backtest.py
"""5通貨・LONG/SHORTでATR期間と相対ボラティリティ基準を比較する。

EMA20/EMA50クロスで売買方向を決め、ATRが自身の過去分布に対して
指定された水準以上のときだけエントリーする。10組のTP/SLも比較する。
"""

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


# GMO-APIフォルダで次のコマンドを実行します。
# python backtest_research/average_true_range/run_atr_backtest.py

ENTRY_TIMEFRAME = "15min"
ENTRY_TIMEFRAME_DURATION = timedelta(minutes=15)
PRICE_TYPE = "ASK"

BACKTEST_START_DATE = date(2024, 1, 1)
BACKTEST_END_DATE = date(2026, 7, 31)

SYMBOLS = ("USD_JPY", "EUR_JPY", "GBP_JPY", "EUR_USD", "AUD_USD")
SIDES = ("LONG", "SHORT")

# EMAクロスは全ATR設定で共通です。ATR自体は売買方向を決めません。
FAST_EMA_PERIOD = 20
SLOW_EMA_PERIOD = 50

# ATRはWilder方式で計算します。
ATR_PERIODS = (7, 10, 14, 20, 21, 28)

# 現在のATRを、直前100本のATRだけから作った分布と比較します。
# 0.0: 制限なし
# 0.3: 下位30%を除外(上位70%を採用)
# 0.5: 下位50%を除外(上位50%を採用)
# 0.7: 下位70%を除外(高ボラ上位30%だけを採用)
ATR_PERCENTILE_LOOKBACK = 100
ATR_FILTERS = (
    ("制限なし", None),
    ("低ボラ30%除外", 0.3),
    ("低ボラ50%除外", 0.5),
    ("高ボラ30%のみ", 0.7),
)

# 利幅・損失幅とリスクリワードの偏りを避けるため、狭い出口から広い出口まで
# 既存のボリンジャーバンド検証と同じ10組を比較します。
TP_SL_PAIRS = (
    (20, 10),
    (30, 10),
    (30, 20),
    (50, 10),
    (50, 20),
    (50, 30),
    (80, 20),
    (80, 30),
    (100, 30),
    (100, 50),
)

# 最長ATR、EMA、過去ATR分布を検証開始前に準備できる本数です。
WARMUP_BARS = max(
    SLOW_EMA_PERIOD + 1,
    max(ATR_PERIODS) + ATR_PERCENTILE_LOOKBACK + 1,
)

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"

RESULT_COLUMNS = (
    "通貨ペア", "売買方向", "短期EMA期間", "長期EMA期間", "ATR期間",
    "ATRフィルター", "ATR下限パーセンタイル", "ATR分布参照本数",
    "TP(pips)", "SL(pips)", "取引回数", "勝ち取引数", "負け取引数",
    "勝率(%)", "合計損益(pips)", "平均損益(pips)",
    "プロフィットファクター", "最大ドローダウン(pips)",
    "平均保有時間(分)", "最終足決済数",
)


def load_candles(path):
    """検証前のウォームアップ足と検証期間内のOHLCを読み込む。"""
    warmup = deque(maxlen=WARMUP_BARS)
    backtest = []
    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
            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:
                warmup.append(candle)
            else:
                backtest.append(candle)
    return list(warmup) + backtest, len(warmup), len(backtest)


def calculate_ema(candles, period):
    """終値のEMAを計算する。初期値には最初のperiod本の単純平均を使う。"""
    if period <= 0:
        raise ValueError("EMA期間は1以上にしてください")
    values = [None] * len(candles)
    if len(candles) < period:
        return values
    initial_index = period - 1
    values[initial_index] = sum(
        candle["close"] for candle in candles[:period]
    ) / period
    multiplier = 2.0 / (period + 1)
    for index in range(initial_index + 1, len(candles)):
        values[index] = (
            candles[index]["close"] - values[index - 1]
        ) * multiplier + values[index - 1]
    return values


def calculate_atr(candles, period):
    """True RangeをWilder方式で平滑化してATRを計算する。"""
    if period <= 0:
        raise ValueError("ATR期間は1以上にしてください")
    atr = [None] * len(candles)
    if len(candles) <= period:
        return atr
    true_ranges = [None] * len(candles)
    for index in range(1, len(candles)):
        current = candles[index]
        previous_close = candles[index - 1]["close"]
        true_ranges[index] = max(
            current["high"] - current["low"],
            abs(current["high"] - previous_close),
            abs(current["low"] - previous_close),
        )
    atr[period] = sum(true_ranges[1:period + 1]) / period
    for index in range(period + 1, len(candles)):
        atr[index] = (
            atr[index - 1] * (period - 1) + true_ranges[index]
        ) / period
    return atr


def calculate_atr_percentile_ranks(atr, lookback):
    """現在値を直前lookback個のATRと比較したパーセンタイル順位を返す。

    現在足のATRを比較分布へ含めないため、フィルターに未来情報は入らない。
    例えば0.7以上なら、現在ATRが過去分布の上位30%相当である。
    """
    if lookback <= 0:
        raise ValueError("ATR分布参照本数は1以上にしてください")
    ranks = [None] * len(atr)
    window = deque()
    sorted_window = []
    for index, value in enumerate(atr):
        if value is None:
            continue
        if len(sorted_window) == lookback:
            # bisect_leftにより「現在値未満」の過去値の割合を求めます。
            ranks[index] = bisect_left(sorted_window, value) / lookback
        window.append(value)
        insort(sorted_window, value)
        if len(window) > lookback:
            # 現在値を追加した後、次回に備えて最古の値を1個だけ削除します。
            old = window.popleft()
            sorted_window.pop(bisect_left(sorted_window, old))
    return ranks


def make_trade(position, candle, exit_price, reason, side, pip_size):
    """決済済み取引の保有時間とpips損益を返す。"""
    direction = 1.0 if side == "LONG" else -1.0
    return {
        "保有時間(分)": (
            candle["close_time"] - position["time"]
        ).total_seconds() / 60,
        "損益(pips)": direction * (exit_price - position["price"]) / pip_size,
        "決済理由": reason,
    }


def collect_trades(candles, fast_ema, slow_ema, atr_ranks, side,
                   minimum_percentile, tp, sl, pip_size, entry_start_index):
    """EMAクロスとATR相対基準で入り、固定TP/SLで決済する。"""
    position = None
    pending_entry = False
    trades = []
    for index in range(1, len(candles)):
        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

        if position is not None:
            tp_distance = tp * pip_size
            sl_distance = sl * pip_size
            if side == "LONG":
                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:
                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
            # 足内の到達順が不明な場合は保守的に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

        ema_values = (
            fast_ema[index - 1], slow_ema[index - 1],
            fast_ema[index], slow_ema[index],
        )
        if position is not None or any(value is None for value in ema_values):
            continue
        if side == "LONG":
            crossed = (
                fast_ema[index - 1] <= slow_ema[index - 1]
                and fast_ema[index] > slow_ema[index]
            )
        else:
            crossed = (
                fast_ema[index - 1] >= slow_ema[index - 1]
                and fast_ema[index] < slow_ema[index]
            )
        # 制限なしではATR順位が未計算でもEMAクロスを採用します。
        atr_allowed = minimum_percentile is None or (
            atr_ranks[index] is not None
            and atr_ranks[index] >= minimum_percentile
        )
        if crossed and atr_allowed:
            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, atr_period, filter_name, minimum_percentile,
              tp, sl, trades):
    """1つのATR設定の全期間成績をまとめる。"""
    profits = [trade["損益(pips)"] for trade in trades]
    wins = [value for value in profits if value > 0]
    losses = [value for value in profits if value < 0]
    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)
    gross_loss = abs(sum(losses))
    durations = [trade["保有時間(分)"] for trade in trades]
    return {
        "通貨ペア": symbol, "売買方向": side,
        "短期EMA期間": FAST_EMA_PERIOD, "長期EMA期間": SLOW_EMA_PERIOD,
        "ATR期間": atr_period, "ATRフィルター": filter_name,
        "ATR下限パーセンタイル": (
            "" if minimum_percentile is None else int(minimum_percentile * 100)
        ),
        "ATR分布参照本数": ATR_PERCENTILE_LOOKBACK,
        "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を保存する。"""
    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,
                   fast_ema, slow_ema):
    """1通貨・1方向でATR期間6種類×基準4種類×出口10組を検証する。"""
    pip_size = 0.01 if symbol.endswith("_JPY") else 0.0001
    rows = []
    for atr_period in ATR_PERIODS:
        atr = calculate_atr(candles, atr_period)
        atr_ranks = calculate_atr_percentile_ranks(
            atr, ATR_PERCENTILE_LOOKBACK,
        )
        for filter_name, minimum_percentile in ATR_FILTERS:
            for tp, sl in TP_SL_PAIRS:
                trades = collect_trades(
                    candles, fast_ema, slow_ema, atr_ranks, side,
                    minimum_percentile, tp, sl, pip_size, entry_start_index,
                )
                rows.append(summarize(
                    symbol, side, atr_period, filter_name, minimum_percentile,
                    tp, sl, trades,
                ))
    write_csv(output_dir / symbol / side / "result.csv", rows)
    return rows


def main():
    """5通貨・2方向・ATR24設定・出口10組の全2,400ケースを実行する。"""
    started = time.perf_counter()
    if BACKTEST_START_DATE > BACKTEST_END_DATE:
        raise SystemExit("検証開始日は検証終了日以前に設定してください")
    if FAST_EMA_PERIOD >= SLOW_EMA_PERIOD:
        raise SystemExit("短期EMA期間は長期EMA期間より短くしてください")
    total_cases = (
        len(SYMBOLS) * len(SIDES) * len(ATR_PERIODS) * len(ATR_FILTERS)
        * len(TP_SL_PAIRS)
    )
    print(
        f"5通貨 × 2方向 × ATR期間{len(ATR_PERIODS)}種類 × "
        f"フィルター{len(ATR_FILTERS)}種類 × "
        f"TP/SL{len(TP_SL_PAIRS)}組 = {total_cases}ケース"
    )
    print(f"検証期間: {BACKTEST_START_DATE} ~ {BACKTEST_END_DATE}")
    print(
        f"共通シグナル: EMA{FAST_EMA_PERIOD}/EMA{SLOW_EMA_PERIOD}クロス / "
        f"ATR分布: 直前{ATR_PERCENTILE_LOOKBACK}本"
    )

    candles_by_symbol = {}
    entry_indexes = {}
    emas_by_symbol = {}
    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:
            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
        emas_by_symbol[symbol] = (
            calculate_ema(candles, FAST_EMA_PERIOD),
            calculate_ema(candles, SLOW_EMA_PERIOD),
        )
        print(
            f"[LOAD] {symbol}: 検証{backtest_count}本 / "
            f"ウォームアップ: {warmup_description}"
        )

    all_rows = []
    cases_per_experiment = (
        len(ATR_PERIODS) * len(ATR_FILTERS) * len(TP_SL_PAIRS)
    )
    for symbol in SYMBOLS:
        for side in SIDES:
            print(f"[RUN] {symbol} {side}: {cases_per_experiment}ケース")
            fast_ema, slow_ema = emas_by_symbol[symbol]
            all_rows.extend(run_experiment(
                candles_by_symbol[symbol], symbol, side,
                entry_indexes[symbol], DEFAULT_OUTPUT_DIR, fast_ema, slow_ema,
            ))
    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())

同じようにATRの期間・ボラティリティ基準・TP/SLを検証してみたい方は、参考にしてみてください。

FXのATRとは

ATR(Average True Range)は、相場の値動きの大きさ(ボラティリティ)を確認するためのテクニカル指標です。

ATRが高いほど値動きが大きく、低いほど値動きが小さいと判断できます。

ただし、ATRは上昇・下落といった相場の方向を判断する指標ではありません。

価格が大きく上昇した場合でも、大きく下落した場合でも、値動きが大きくなればATRは上昇します。

そのため、ATRは売買方向を決めるというより、現在の相場がどの程度動いているかを確認するための指標と考えると分かりやすいでしょう。

ATRで分かること

ATRでは、現在の相場のボラティリティが高いか低いかを確認できます。

基本的には、ATRが高いほど値動きが大きく、低いほど値動きが小さい状態です。

たとえばUSDJPYの15分足で、ATRが5pipsから10pips、15pipsと上昇していれば、直近の値動きが徐々に大きくなっていると判断できます。

反対に、15pipsから10pips、5pipsと低下していれば、値動きが落ち着いている状態です。

ただし、ATRの上昇は価格の上昇を意味しません。急落した場合でも値動きが大きければATRは上昇するため、相場の方向ではなく値動きの大きさを見る指標として使います。

ATRの計算方法

ATRを計算するには、まずTR(True Range:真の値幅)を求めます。

TRは「高値-安値」「高値と前回終値の差」「安値と前回終値の差」の3つを比較し、最も大きい値を採用します。

たとえば、前回終値が150.00円、現在の高値が150.30円、安値が150.10円の場合、「高値-安値」は0.20円ですが、「高値-前回終値」は0.30円です。

この場合、TRは最も大きい0.30円となります。

ATRは、このTRを一定期間にわたって平滑化して求めます。一般的には14期間が使われますが、期間を短くすると直近の値動きに反応しやすく、長くすると変化が緩やかになります。

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

ATRでは、14期間が一般的な設定としてよく使われています。

たとえば15分足でATR14を設定した場合、直近14本の15分足をもとに値動きの大きさを確認します。

ATRは期間を短くすると直近の値動きに反応しやすくなり、長くすると一時的な変動の影響を受けにくいです。

たとえばATR7はATR14より素早く変化しやすい一方、ATR21やATR28では値動きがより滑らかに表示されます。

ただし、14期間がすべての通貨ペアや売買条件で最適とは限りません。

そこで今回は、一般的なATR14だけでなく、7・10・14・20・21・28の6種類を使い、5通貨のバックテスト結果から成績の違いを比較していきます。

今回のATR検証条件

今回の検証では、ATRの期間やボラティリティの基準によって成績がどのように変わるのかを確認します。

主な検証条件は次の5つです。

  • 5通貨・15分足を使用
  • ATR期間は6種類
  • ATRフィルターは3種類
  • LONG・SHORTをそれぞれ検証
  • TP・SLは10パターンを比較

それぞれの条件を組み合わせてバックテストを行い、合計損益やPF、最大ドローダウンなどを比較します。

検証する5通貨と期間

今回の検証では、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を使用します。

エントリー判定には15分足を使い、検証期間は2024年1月1日〜2026年7月31日です。

また、LONGとSHORTは別々に集計し、同じATR設定でも売買方向によって成績に違いが出るか確認します。

価格データに使用するのはASKです。

5通貨を同じルールで検証することで、特定の通貨だけで好成績だった設定ではなく、複数通貨で使いやすいATR設定があるのかも比較していきます。

検証するATR期間6種類・フィルター3種類

ATR期間は、7・10・14・20・21・28の6種類を検証します。

さらに、現在のATRを直前100本のATRと比較し、ボラティリティが低い相場を除外する3種類のフィルターを使用します。

  • 低ボラ30%を除外
  • 低ボラ50%を除外
  • 高ボラ上位30%のみ

このほか、比較用としてATRフィルターを使用しない条件も用意します。

一般的によく使われるATR14だけでなく、期間やボラティリティ基準を変えることで、成績にどのような違いが出るのか確認します。

LONG・SHORTのエントリー条件

ATRは相場の値動きの大きさを確認する指標であり、ATRだけでは上昇・下落の方向を判断できません。

そのため今回の検証では、EMA20とEMA50のクロスを使って売買方向を決めます。

LONGはEMA20がEMA50を下から上へ抜けた場合、SHORTはEMA20がEMA50を上から下へ抜けた場合をエントリー候補とするのです。

そのうえでATRフィルターの条件を満たしている場合のみ、シグナルが確定した次の15分足の始値でエントリーします。

TP・SL10パターンで検証

ATR設定だけでなく、利確・損切り幅による偏りを抑えるため、今回はTP・SLを10パターン用意しました。

具体的には、TP20〜100pips、SL10〜50pipsの組み合わせの比較です。

たとえば「TP20・SL10」「TP50・SL30」「TP100・SL50」など、狭い値幅から広い値幅まで検証します。

なお、同じ15分足の中でTPとSLの両方に到達した場合は、値動きの順番を判断できないため、保守的にSLへ到達したものとして処理します。

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

バックテスト結果では、単純な合計損益だけでなく、複数の指標を確認します。

主に比較するのは、取引回数・勝率・合計損益・平均損益・プロフィットファクター(PF)・最大ドローダウンです。

たとえば合計pipsが大きくても、最大ドローダウンが極端に大きい設定や、取引回数が少なすぎる設定は安定しているとは言い切れません。

そのため、利益の大きさだけでなく、PFや最大ドローダウン、取引回数も含めて総合的にATR設定を比較していきます。

5通貨でATR設定を検証

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

今回は、

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

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

そのうえでPFの高い順に並べ、ATR期間・フィルター・TP/SL・合計pips・最大ドローダウンもあわせて確認します。

なお、ATRフィルターを使用しない「制限なし」は、ATR期間を変更しても同じ取引結果になるため、今回のランキングからは除外しています。

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

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

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

USDJPY LONG:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
17高ボラ30%のみ100/50118+1,7501.5224350
221高ボラ30%のみ100/50129+1,8001.4865400
320高ボラ30%のみ100/50132+1,8001.4737400
428低ボラ50%除外100/50182+2,4501.4667500
514高ボラ30%のみ100/50127+1,6001.4324500

USDJPY LONGの1位は、ATR期間7・高ボラ30%のみ・TP100・SL50です。

118回の取引で+1,750pips、PF1.5224、最大DD350pipsとなりました。

上位5設定のうち4つが「高ボラ30%のみ」となっており、TP100・SL50についてはすべて共通しています。

一方、4位のATR28・低ボラ50%除外はPFでは1位に届かなかったものの、+2,450pipsと上位5設定で最も大きな利益となりました。

今回のUSDJPY LONGでは、値動きが大きい場面に絞り、比較的広めのTP・SLを設定する組み合わせがPF上位に入りました。

USDJPY SHORT:基準を満たす設定なし

USDJPY SHORTでは、今回設定した

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

の3条件をすべて満たすATR設定はありませんでした。

そのため、USDJPYではSHORTよりもLONGの方がATRフィルターを組み合わせた設定で良好な結果が多い結果となっています。

ただし、今回のEMA20・EMA50クロスやTP/SLとの組み合わせによる結果であり、ATRそのものがUSDJPY SHORTに向いていないと判断できるわけではありません。

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

EURJPY LONG:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
17高ボラ30%のみ80/20190+1,4001.5072620
27高ボラ30%のみ100/30174+1,6701.4601600
310高ボラ30%のみ80/20180+1,2001.4545620
47高ボラ30%のみ50/10202+6201.3924420
57高ボラ30%のみ80/30181+1,3901.3894630

EURJPY LONGの1位は、ATR期間7・高ボラ30%のみ・TP80・SL20です。

190回の取引で+1,400pips、PF1.5072となりました。

上位5設定すべてで「高ボラ30%のみ」が採用されており、そのうち4設定がATR期間7です。

また、TP・SLについては複数の組み合わせが上位に入っています。

今回のEURJPY LONGでは、ATR期間を比較的短くし、過去100本と比較してボラティリティが高い場面に絞る設定が良好な結果となりました。

EURJPY SHORT:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
114低ボラ50%除外50/10330+6601.2500240
27高ボラ30%のみ50/10259+4101.1962340
310低ボラ50%除外50/10353+4901.1713320
414高ボラ30%のみ50/10217+2901.1648290
528低ボラ30%除外50/10395+4301.1335300

EURJPY SHORTの1位は、ATR期間14・低ボラ50%除外・TP50・SL10です。

330回の取引で+660pips、PF1.2500、最大DD240pipsとなりました。

LONGとは異なり、上位設定のATR期間やフィルターにはばらつきがあります。

一方で、上位5設定のTP50・SL10はすべて共通しています。

EURJPY SHORTではATR期間だけでなく、出口条件との組み合わせも成績に大きく影響していると考えられます。

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

GBPJPY LONG:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
120高ボラ30%のみ80/20186+6801.2394520
221高ボラ30%のみ80/20186+6801.2394520
328高ボラ30%のみ80/20189+5201.1781580
47低ボラ30%除外100/30387+1,5201.1772720
57低ボラ50%除外100/30305+1,1201.1652790

GBPJPY LONGでは、ATR期間20と21・高ボラ30%のみ・TP80・SL20が同率でPF1.2394となりました。

どちらも186回の取引で+680pips、最大DD520pipsです。

PF上位3設定はいずれも高ボラ30%のみとなりましたが、4位のATR7・低ボラ30%除外では**+1,520pips**と大きな利益を記録しています。

そのため、GBPJPY LONGではPFを重視する場合と合計利益を重視する場合で、選ばれるATR設定が変わる結果となりました。

GBPJPY SHORT:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
128高ボラ30%のみ50/10209+1301.0756200
27低ボラ50%除外50/10390+2401.0748300
314高ボラ30%のみ30/20238+1901.0683250
47低ボラ30%除外50/10517+2901.0681370
57高ボラ30%のみ30/20273+1901.0594240

GBPJPY SHORTの1位は、ATR期間28・高ボラ30%のみ・TP50・SL10です。

209回の取引で+130pips、PF1.0756、最大DD200pipsとなりました。

ただし、上位5設定のPFはいずれも1.05〜1.08程度であり、LONGと比べると優位性は大きくありません。

合計pipsも最大で+290pipsにとどまっています。

そのため、今回のGBPJPYではSHORTよりも、LONGの方が比較的良好な結果が確認できたと考えられます。

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

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

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
120高ボラ30%のみ100/50104+6501.2000350
221高ボラ30%のみ100/50103+5501.1692350
320高ボラ30%のみ100/30122+2401.0870510
421高ボラ30%のみ100/30121+1401.0507510

EURUSD LONGで基準を満たしたのは、4設定のみでした。

1位はATR期間20・高ボラ30%のみ・TP100・SL50で、104回の取引で+650pips、PF1.2000、最大DD350pipsです。

4設定すべてに共通しているのが、ATR期間20〜21・高ボラ30%のみ・TP100という点です。

今回のEURUSD LONGでは、短いATR期間よりも20〜21程度の期間を使い、ボラティリティが高い場面だけに絞った設定が基準を満たしました。

EURUSD SHORT:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
110高ボラ30%のみ100/50112+448.81.1264550
27低ボラ50%除外20/10351+2701.1200260
328低ボラ50%除外30/20245+3001.1064230
47高ボラ30%のみ100/50122+398.81.1022550
520低ボラ50%除外20/10279+1801.1000250

EURUSD SHORTの1位は、ATR期間10・高ボラ30%のみ・TP100・SL50です。

112回の取引で+448.8pips、PF1.1264となりました。

ただし、上位5設定を見るとATR期間・フィルター・TP/SLはいずれもばらついています。

LONGのように特定の設定へ結果が集中していないため、EURUSD SHORTでは1つのATR期間だけを最適設定と判断するのは難しい結果となりました。

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

AUDUSD LONG:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
17低ボラ30%除外50/10397+4701.1455430
210低ボラ30%除外50/10394+2601.0802590
37低ボラ30%除外30/10427+2501.0796230
47低ボラ50%除外50/10305+1901.0757390
510低ボラ30%除外30/10422+2201.0707330

AUDUSD LONGの1位は、ATR期間7・低ボラ30%除外・TP50・SL10です。

397回の取引で+470pips、PF1.1455、最大DD430pipsとなりました。

上位5設定を見ると、ATR期間は7または10となっており、4設定で「低ボラ30%除外」が使われています。

今回のAUDUSD LONGでは、高ボラ相場だけに限定するよりも、低ボラティリティの場面をある程度除外する使い方がPF上位に入りました。

AUDUSD SHORT:TOP5

順位ATR期間ATRフィルターTP/SL取引回数合計pipsPF最大DD
110高ボラ30%のみ50/10189+7501.5172140
27高ボラ30%のみ50/10204+7201.4557140
310高ボラ30%のみ30/10195+5301.3985140
410高ボラ30%のみ20/10201+4501.3782120
528高ボラ30%のみ30/10175+4501.3750110

AUDUSD SHORTの1位は、ATR期間10・高ボラ30%のみ・TP50・SL10です。

189回の取引で+750pips、PF1.5172、最大DDはわずか140pipsとなりました。

さらに上位5設定は、すべて「高ボラ30%のみ」かつSL10pipsです。

ATR期間についても、上位4設定中3つがATR10となっています。

今回の5通貨の中でもAUDUSD SHORTは特に傾向が分かりやすく、ボラティリティが高い場面に絞ることで良好な結果が得られた設定が多いことが分かりました。

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

5通貨の検証結果を比較すると、通貨ペアや売買方向によって成績が良いATR設定は異なりました

各通貨でPFが最も高かった設定は、次のとおりです。

通貨ペア方向ATR期間フィルターPF
USDJPYLONG7高ボラ30%のみ1.5224
EURJPYLONG7高ボラ30%のみ1.5072
EURJPYSHORT14低ボラ50%除外1.2500
GBPJPYLONG20高ボラ30%のみ1.2394
GBPJPYSHORT28高ボラ30%のみ1.0756
EURUSDLONG20高ボラ30%のみ1.2000
EURUSDSHORT10高ボラ30%のみ1.1264
AUDUSDLONG7低ボラ30%除外1.1455
AUDUSDSHORT10高ボラ30%のみ1.5172

※USDJPY SHORTは基準を満たす設定がありませんでした。

今回の結果では、高ボラ30%のみでエントリーする設定が多くの通貨で上位となりました。

また、一般的なATR14が常に最も良いわけではなく、ATR7・10・20なども上位に入っています。

そのため、ATRは14期間に固定するのではなく、通貨ペアや売買方向に合わせて期間やフィルターを調整することが重要と考えられます。

ATRをFXで使う際の注意点

ATRは相場のボラティリティを確認するのに便利な指標ですが、使い方にはいくつか注意点があります。

主なポイントは次の4つです。

  • ATRだけでエントリー方向を判断しない
  • 通貨ペアや売買方向によって適した設定が異なる
  • ATRは高ければ高いほど良いわけではない
  • バックテスト結果がそのまま実運用の成績になるわけではない

1つずつ詳しく見ていきましょう。

ATRだけでエントリー方向を判断しない

ATRは、相場の値動きの大きさを確認する指標であり、上昇・下落の方向を判断するものではありません。

たとえば相場が急上昇した場合も、急落した場合も、値動きが大きくなればATRは上昇します。

そのため、「ATRが高いからLONG」「ATRが低いからSHORT」といった使い方はできません。

実際のトレードでは、移動平均線などで売買方向を判断し、ATRはエントリーする相場環境を絞り込むためのフィルターとして使う方法があります。

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

今回の検証では、すべての通貨ペア・売買方向で同じATR設定が好成績になったわけではありません

たとえばUSDJPY LONGではATR7、EURJPY SHORTではATR14、GBPJPY LONGではATR20がPF上位となっています。

また、同じ通貨ペアでもLONGとSHORTで良かった期間やフィルターが異なるケースもありました。

そのため、一般的なATR14だけに固定するのではなく、使用する通貨ペアや売買ルールに合わせてバックテストすることが重要です。

ATRは高ければ高いほど良いわけではない

ATRが高いと値動きが大きいため、利益を狙いやすいように見えますが、ボラティリティが高ければ必ず成績が良くなるわけではありません

今回の検証では「高ボラ30%のみ」が上位に入る通貨が多かった一方、EURJPY SHORTでは「低ボラ50%除外」、AUDUSD LONGでは「低ボラ30%除外」がPF1位でした。

つまり、極端に高いボラティリティだけを狙うよりも、低ボラ相場を適度に除外する方が合うケースもあるということです。

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

今回のバックテストで好成績だったATR設定でも、実際の取引で同じ結果になるとは限りません

バックテストは過去の値動きを使った検証であり、今後も同じ相場環境が続く保証はないです。

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

そのため、バックテスト結果だけで設定を決めるのではなく、直近の相場でも機能しているかを確認しながら利用することが重要です。

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

最後に、FXでATRを使う際によくある質問をまとめます。

主な疑問は次の5つです。

  • ATRのおすすめ期間は?
  • ATR14がよく使われる理由は?
  • ATRは何pipsを基準にすればいい?
  • ATRとADXの違いは?
  • ATRはどの時間足で使える?

1つずつ見ていきましょう。

ATRのおすすめ期間は?

ATRでは14期間が一般的ですが、必ずしもすべての通貨ペアで最適とは限りません。

今回の検証でも、PF上位にはATR7・10・14・20・28など幅広い期間が入りました。

たとえばUSDJPY LONGではATR7、EURJPY SHORTではATR14、GBPJPY LONGではATR20が上位となっています。

そのため、まずはATR14を基準にしつつ、実際に使う通貨ペアや売買ルールに合わせて複数期間をバックテストする方法がおすすめです。

ATR14がよく使われる理由は?

ATR14がよく使われるのは、ATRを考案したJ. Welles Wilder Jr.が14期間を基本設定として使用したことが大きな理由です。

14期間は、短すぎず長すぎないため、直近の値動きへの反応と数値の安定性のバランスを取りやすい設定です。

ただし、14という数字自体がどの相場でも最適という意味ではありません。

今回の検証でもATR14以外が上位になるケースが多かったため、14はあくまで基準となる設定の1つと考えるとよいでしょう。

ATRは何pipsを基準にすればいい?

ATRに「○pips以上なら必ずエントリー」といった共通の基準はありません。

たとえばUSDJPYとGBPJPYでは普段の値動きが異なるため、同じ10pipsでも意味が変わります。

そのため、固定pipsで判断するより、その通貨ペアの過去のATRと比較する方法が使いやすいです。

今回の検証では、現在のATRを直前100本と比較し、「低ボラ30%除外」「低ボラ50%除外」「高ボラ30%のみ」という相対的な基準を使って検証しました。

ATRとADXの違いは?

ATRとADXは、どちらも相場環境を確認するために使われますが、見ているものが異なります。

ATRは値動きの大きさ、ADXはトレンドの強さを確認する指標です。

たとえばATRが高くても、価格が上下に激しく動いているだけで明確なトレンドが出ていない場合があります。

一方、ADXが高い場合は、一定方向へのトレンドが強まっていると判断できます。

そのため、ATRはボラティリティ、ADXはトレンドの強さを見るものとして使い分けると分かりやすいでしょう。

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

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

ただし、同じATR14でも時間足によって意味は変わります。

たとえば15分足のATR14は直近14本の15分足、1時間足のATR14は直近14本の1時間足をもとに計算します。

そのため、短期売買なら短い時間足、中長期のトレードなら1時間足や4時間足など、自分のトレードスタイルに合わせた時間足で確認することが重要です。

まとめ

今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、ATR期間6種類・ATRフィルター・TP/SL10パターン・LONG/SHORTを組み合わせた2,400ケースを検証しました。

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

  • 一般的なATR14がすべての通貨で最適とは限らない
  • ATR7・10・20などもPF上位に入った
  • 通貨ペアやLONG・SHORTによって成績の良いATR期間が異なる
  • 高ボラ30%のみでエントリーする設定が多くの通貨で上位となった
  • 低ボラを30〜50%除外した方が良いケースもあった
  • USDJPY SHORTでは今回の基準を満たす設定が見つからなかった
  • ATRは期間だけでなく、ボラティリティ基準やTP・SLも含めて検証することが重要

今回、取引回数100回以上・合計pipsがプラス・PF1.05以上という条件を満たした設定の中で、最も高いPFとなったのはUSDJPY LONGのATR期間7・高ボラ30%のみ・TP100・SL50でPF1.5224でした。

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

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

実際にATRをトレードへ取り入れる場合は、ATR14などの一般的な設定をそのまま使うのではなく、通貨ペアや売買方向、TP・SL、直近の相場環境を含めてバックテストしたうえで判断してください。

-FX自動売買, Python