FX / EA Python

FXのEMA(指数平滑移動平均線)の最適な期間設定は?5通貨で100通りを検証

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

FXでEMA(指数平滑移動平均線)を使うとき、

  • EMAは何期間に設定すればいいの?
  • 20・50・100・200など、どの期間が使いやすいの?
  • SMAよりEMAの方が良いの?

と悩む方も多いのではないでしょうか。

EMAにはよく使われる期間がありますが、実際にどの設定が機能するかは、通貨や売買方向によって異なります。

そこで今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、短期EMA10種類×長期EMA10種類の100通りをLONG・SHORTそれぞれで検証しました。

単純な合計利益だけではなく、PF・最大DD・直近6か月の成績・周辺設定の安定性なども含めて評価し、成績が良かったEMAの期間設定を紹介します。

また、全期間では良くても直近では成績が悪化している設定も確認し、長期・直近の両方で安定しているEMA設定があるのかも見ていきます。

「FXでEMAの期間をどう設定すればいいか迷っている」という方は、ぜひ参考にしてください。

FXのEMA(指数平滑移動平均線)とは

EMA(Exponential Moving Average:指数平滑移動平均線)とは、直近の価格をより重視して計算する移動平均線です。

一定期間の価格を平均して値動きの方向を確認する点はSMA(単純移動平均線)と同じですが、EMAは新しい価格ほど大きな比重を置いて計算します。

そのため、SMAと比べて現在の値動きへ素早く反応しやすいのが特徴です。

FXでは、相場のトレンド方向を確認したり、短期EMAと長期EMAのクロスをエントリー・決済の判断に利用したりします。

今回の記事では、短期EMAと長期EMAの期間を変えながら、どの組み合わせが実際のFX相場で成績につながりやすかったのかを検証していきます。

EMAとSMAの違い

EMAとSMAはどちらも移動平均線ですが、価格に対する重み付けの方法が異なります

SMAは、指定した期間内の価格をすべて同じ重みで平均します。

一方のEMAは、直近の価格ほど大きな重みを付けて計算するため、SMAよりも最近の値動きを反映しやすくなります。

項目EMASMA
正式名称指数平滑移動平均線単純移動平均線
価格の重み直近の価格を重視すべて同じ
値動きへの反応比較的速い比較的緩やか
特徴トレンド変化を早めに捉えやすい値動きが滑らかになりやすい

例えば、相場が急上昇した場合、直近価格を重視するEMAの方がSMAより早く上向きやすくなります。

ただし、EMAの方が必ず優れているわけではありません

価格への反応が速い分、短期的な値動きにも反応しやすくなるため、相場環境や使用する期間によって成績は変わります。

そのため、今回は「EMAだから有効」と考えるのではなく、実際のローソク足データを使って期間ごとの成績を比較します。

EMAでよく使われる期間

EMAにも「この期間を使えば必ず勝てる」という決まった設定はありません。

取引する時間足やトレードスタイルによって、使われる期間は異なります。

例えば、短期の値動きを確認するEMAとして5・10・20前後、中期では50・75、長期では100・200などが使われることがあります。

しかし、一般的によく使われている期間が、そのまま最も高い成績になるとは限りません。

また、同じEMA期間でも、

  • 通貨ペア
  • 時間足
  • LONG・SHORT
  • 相場環境

などによって結果が変わる可能性があります。

そこで今回の検証では、一般的な期間だけに限定せず、比較範囲を広げるために以下のEMAを使用します。

  • 短期EMA:5、10、15、20、25、30、35、40、45、50
  • 長期EMA:60、75、100、120、150、200、250、300、400、500

短期10種類×長期10種類で、1通貨・1方向につき100通りのEMA設定を比較します。

ゴールデンクロスとデッドクロス

2本のEMAを使った代表的な売買シグナルが、ゴールデンクロスとデッドクロスです。

ゴールデンクロスは、短期EMAが長期EMAを下から上へ抜ける状態です。

反対にデッドクロスは、短期EMAが長期EMAを上から下へ抜ける状態を指します。

今回の検証では、次の条件でLONG・SHORTを判定します。

方向エントリー決済
LONGゴールデンクロスデッドクロス
SHORTデッドクロスゴールデンクロス

例えばLONGの場合、短期EMAが長期EMAを上抜けたことを確認して買い、その後、短期EMAが長期EMAを下抜けたら決済します。

SHORTではその反対です。

なお、クロスしたローソク足の終値で売買すると、その足が確定する前の情報を利用してしまう可能性があります。

そのため今回のバックテストでは、EMAのクロスが確定した次の15分足の始値でエントリー・決済する条件としています。

この条件を統一したうえで、5通貨・100通りのEMA期間をLONG・SHORTそれぞれで比較していきます。

今回のEMAの検証条件

今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、15分足データを使ってEMAの期間設定を検証します。

1通貨・1方向につき100通りのEMA設定を比較し、LONG・SHORTをそれぞれ独立して検証します。

また、単純に合計pipsが大きい設定を選ぶのではなく、PF・最大DD・直近6か月の成績・周辺設定の安定性なども含めて評価します。

今回検証する通貨・期間・EMA

今回の主な検証条件は以下のとおりです。

項目検証条件
通貨USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSD
時間足15分足
移動平均線EMA(指数平滑移動平均線)
短期EMA5・10・15・20・25・30・35・40・45・50
長期EMA60・75・100・120・150・200・250・300・400・500
組み合わせ1通貨・1方向につき100通り
検証期間2024年1月1日 ~ 2026年7月31日
直近6か月2026年2月1日 ~ 2026年7月31日
価格データGMO FX APIで取得したローソク足
使用価格ASK

今回は、EMAそのものの期間設定による違いを確認するため、1時間足・4時間足・日足などの上位足フィルターは使用しません。

また、固定のTP・SLも設定せず、反対方向のEMAクロスが発生するまでポジションを保有する条件としています。

短期EMA10種類×長期EMA10種類を組み合わせるため、1通貨・1方向につき100通りです。

LONG・SHORTをそれぞれ検証するため、5通貨では合計1,000パターンを比較します。

なお、今回の検証ではASK価格のみを使用し、以下の取引コストは考慮していません

  • スプレッド
  • スリッページ
  • 手数料
  • スワップ

そのため、平均利益が小さい設定やPFが1に近い設定については、実際の取引では成績が悪化する可能性があります

また、今回使用するデータはGMO APIを利用して取得しています。興味のある方は以下の記事を参考にしてください!

LONG・SHORTの検証条件

LONGとSHORTは、それぞれ独立した条件で検証します。

項目LONGSHORT
エントリーゴールデンクロスデッドクロス
約定クロス確定後の次足始値クロス確定後の次足始値
決済デッドクロス後の次足始値ゴールデンクロス後の次足始値
TP・SL使用しない使用しない
同時保有最大1ポジション最大1ポジション
データ終了時最終足終値で決済最終足終値で決済

LONGでは、短期EMAが長期EMAを下から上へ抜けたゴールデンクロスを確認したあと、次の15分足の始値でエントリーします。

その後、短期EMAが長期EMAを上から下へ抜けるデッドクロスが発生した場合、次の15分足の始値で決済します。

SHORTはその反対です。

クロスが発生したローソク足の終値ではなく、クロス確定後の次足始値で売買することで、確定していないローソク足を利用しない条件としています。

また、検証期間の終了時点でポジションが残っている場合は、最後のローソク足の終値で決済します。

成績が良かったEMA設定の選定基準

今回検証した100通りのEMA設定は、単純に合計pipsが大きい順だけで順位を決めません。

特定の期間だけ偶然好成績だった設定や、過去には利益が出ていても直近では機能していない設定をできるだけ避けるため、まず一定の条件で候補を絞り込みます。

足切り条件

  • 全検証期間の合計pipsがプラス
  • 全検証期間のPFが1.05以上
  • 全検証期間の取引回数が100回以上
  • 直近6か月の合計pipsがプラス
  • 直近6か月のうち3か月以上がプラス

直近6か月は、データ最終日の2026年7月31日から遡り、2026年2月1日~2026年7月31日を対象とします。

月別成績は決済日時を基準として集計します。

そのため、2026年2月より前にエントリーし、2月以降に決済された取引については、直近6か月の成績へ含めます。

足切り条件を通過した設定については、以下の7項目を100点満点で評価します。

評価項目配点
全検証期間の合計pips20点
PF15点
利益 ÷ 最大DD20点
平均pips10点
周辺パラメータの安定性15点
直近6か月の成績15点
取引回数5点
合計100点

各項目は、足切り条件を通過した候補の中で0~満点になるように正規化して評価します。

特に重視するのが、利益に対する最大DDと周辺パラメータの安定性です。

例えばEMA45/75だけが好成績でも、その周辺設定がすべて条件を満たしていなければ、偶然その設定だけが良かった可能性があります。

そこで、短期EMA・長期EMAの期間一覧上で、上下左右・斜めを含む最大8設定を周辺設定として確認します。

周辺安定性は「足切り条件を通過した周辺設定数 ÷ 存在する周辺設定数」で算出します。

例えば、周囲8設定のうち8設定すべてが足切り条件を通過していれば、周辺安定性は100%です。

反対に、周辺設定がほとんど条件を満たしていない場合は、特定のEMA設定だけが突出している可能性があるため評価を下げます。

最終的に、足切り条件を通過した設定の中から、総合評価が高かったEMA設定を最大5つまで掲載します。

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

また、条件を満たす設定がない場合は、「今回の条件では有効なEMA設定なし」として結果を紹介します。

今回のEMA検証に使用したPythonスクリプト

今回の検証に使用したPythonスクリプトも掲載しておきます。

このスクリプトでは、短期EMA10種類×長期EMA10種類の100通りについて、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨をLONG・SHORTそれぞれで検証できます。

検証するEMA期間は以下のとおりです。

  • 短期EMA:5、10、15、20、25、30、35、40、45、50
  • 長期EMA:60、75、100、120、150、200、250、300、400、500

クロスが確定した次の15分足の始値でエントリーし、反対方向のクロスが発生した次の足の始値で決済する条件です。

気になる方は、以下のコードをコピーして使ってみてください。

run_ema_backtest.py
"""15分足のEMAクロスを100パターン一括検証する。

シグナルが確定した次の15分足の始値で約定し、反対クロスで決済する。
EMAの初期値には各期間と同じ本数の終値のSMAを使用する。
標準ライブラリだけで実行できる。
"""

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


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

# 検証する5つの通貨ペアです。
SYMBOLS = ("USD_JPY", "EUR_JPY", "GBP_JPY", "EUR_USD", "AUD_USD")

# EMAの計算、クロス判定、売買には15分足だけを使用します。
ENTRY_TIMEFRAME = "15min"
ENTRY_TIMEFRAME_DURATION = timedelta(minutes=15)

# 既存のローソク足CSVに合わせ、ASK価格を読み込みます。
# スプレッドなどの取引コストは、このスクリプトでは考慮しません。
PRICE_TYPE = "ASK"

# バックテストで使用する期間です。
# CSV内のtimestamp_jstの日付が、この開始日から終了日までの足だけを使います。
# 開始日と終了日のローソク足も検証対象に含みます。
BACKTEST_START_DATE = date(2024, 1, 1)
BACKTEST_END_DATE = date(2026, 7, 31)

# 短期EMA10種類と長期EMA10種類の組み合わせを検証します。
# すべて「短期 < 長期」なので、10 × 10 = 100パターンです。
DEFAULT_FAST_PERIODS = (5, 10, 15, 20, 25, 30, 35, 40, 45, 50)
DEFAULT_SLOW_PERIODS = (60, 75, 100, 120, 150, 200, 250, 300, 400, 500)

# __file__は、このPythonファイル自身の場所を表します。
# どのフォルダから実行しても、同じ入力・出力先を参照できます。
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"

# EMA設定ごとの集計結果に保存する列です。
RESULT_COLUMNS = (
    "通貨ペア",
    "短期EMA期間",
    "長期EMA期間",
    "取引回数",
    "勝ち取引数",
    "負け取引数",
    "勝率(%)",
    "合計損益(pips)",
    "平均損益(pips)",
    "プロフィットファクター",
    "最大ドローダウン(pips)",
)

# 1回ごとの取引履歴に保存する列です。
TRADE_COLUMNS = (
    "取引番号",
    "エントリー日時",
    "エントリー価格",
    "決済日時",
    "決済価格",
    "決済理由",
    "保有時間(分)",
    "損益(pips)",
)


def load_candles(path):
    """fetch_gmo_candles.pyが作るOHLC CSVを読み込む。"""
    candles = []

    # CSVの日時、始値、終値だけを読み込みます。
    # 今回は高値・安値による利確や損切りを行わないため、highとlowは不要です。
    with path.open("r", encoding="utf-8-sig", newline="") as file:
        for row in csv.DictReader(file):
            timestamp = datetime.fromisoformat(row["timestamp_jst"])

            # 指定期間より前または後のローソク足は、検証に使用しません。
            # datetimeではなくdate同士で比較するため、終了日の23:45足なども含まれます。
            if not BACKTEST_START_DATE <= timestamp.date() <= BACKTEST_END_DATE:
                continue

            candles.append(
                {
                    "open_time": timestamp,
                    "close_time": timestamp + ENTRY_TIMEFRAME_DURATION,
                    "open": float(row["open"]),
                    "close": float(row["close"]),
                }
            )
    return candles


def calculate_ema(closes, period):
    """期間本数のSMAを初期値としてEMAを未来参照なしで計算する。"""
    if period <= 0:
        raise ValueError("EMA期間は1以上である必要があります")

    # EMAをまだ計算できない場所にはNoneを入れます。
    # 例えばEMA3なら、最初の2本はNoneになります。
    values = [None] * len(closes)
    if len(closes) < period:
        return values

    # EMAの最初の値には、同じ期間の単純移動平均(SMA)を使います。
    # 終値が[1, 2, 3]でEMA3なら、最初のEMAは(1 + 2 + 3) / 3 = 2です。
    seed_index = period - 1
    seed = sum(closes[:period]) / period
    values[seed_index] = seed

    # EMAは直近価格へ大きな重みを置く移動平均です。
    # EMA3ならalphaは2 / (3 + 1) = 0.5になります。
    alpha = 2.0 / (period + 1.0)
    previous_ema = seed
    for index in range(period, len(closes)):
        # 現在EMA = alpha × 現在終値 + (1 - alpha) × 前回EMA
        # 現在までに確定した終値だけを使うため、未来の価格は参照しません。
        previous_ema = alpha * closes[index] + (1.0 - alpha) * previous_ema
        values[index] = previous_ema
    return values


def crossover(previous_fast, previous_slow, fast, slow):
    """ゴールデンクロスなら1、デッドクロスなら-1、それ以外は0。"""
    # 短期EMAが長期EMAを下から上へ抜けた状態です。
    if previous_fast <= previous_slow and fast > slow:
        return 1

    # 短期EMAが長期EMAを上から下へ抜けた状態です。
    if previous_fast >= previous_slow and fast < slow:
        return -1
    return 0


def collect_trades(
    candles,
    fast_ema,
    slow_ema,
    side,
    pip_size,
    evaluation_start_index,
):
    """片側戦略を検証し、1回ごとの取引履歴を返す。"""
    if side not in {"LONG", "SHORT"}:
        raise ValueError("sideはLONGまたはSHORTである必要があります")

    # LONGはゴールデンクロスで入り、デッドクロスで出ます。
    # SHORTはデッドクロスで入り、ゴールデンクロスで出ます。
    entry_signal = 1 if side == "LONG" else -1
    exit_signal = -entry_signal

    # entry_priceがNoneなら、ポジションを持っていない状態です。
    # pendingは、前の足で確定した注文を次の足で実行するための印です。
    entry_price = None
    entry_time = None
    pending_entry = False
    pending_exit = False
    trades = []

    # 全EMA設定を公平に比べるため、EMA500を計算できる共通位置から始めます。
    for index in range(max(1, evaluation_start_index), len(candles)):
        # 前足で確定した注文だけを現在足の始値で約定する。
        if entry_price is not None and pending_exit:
            direction = 1.0 if side == "LONG" else -1.0
            exit_price = candles[index]["open"]
            trades.append(
                make_trade(
                    len(trades) + 1,
                    entry_time,
                    entry_price,
                    candles[index]["open_time"],
                    exit_price,
                    "反対クロス",
                    direction,
                    pip_size,
                )
            )
            entry_price = None
            entry_time = None
        pending_exit = False

        # 前の15分足でエントリー条件が確定していれば、現在足の始値で入ります。
        # クロスした足の終値で約定させないことで、未来参照を防ぎます。
        if entry_price is None and pending_entry:
            entry_price = candles[index]["open"]
            entry_time = candles[index]["open_time"]
        pending_entry = False

        # 前の足と現在足の短期・長期EMAを比較してクロスを判定します。
        previous_fast = fast_ema[index - 1]
        previous_slow = slow_ema[index - 1]
        current_fast = fast_ema[index]
        current_slow = slow_ema[index]
        if None in (previous_fast, previous_slow, current_fast, current_slow):
            continue

        signal = crossover(
            float(previous_fast),
            float(previous_slow),
            float(current_fast),
            float(current_slow),
        )

        # ここでは注文を即時約定せず、次のループで次足始値を使います。
        if entry_price is None and signal == entry_signal:
            pending_entry = True
        elif entry_price is not None and signal == exit_signal:
            pending_exit = True

    # データ終了時にポジションが残っていれば、最終足の終値で決済します。
    if entry_price is not None:
        direction = 1.0 if side == "LONG" else -1.0
        trades.append(
            make_trade(
                len(trades) + 1,
                entry_time,
                entry_price,
                candles[-1]["close_time"],
                candles[-1]["close"],
                "データ終了",
                direction,
                pip_size,
            )
        )
    return trades


def make_trade(
    number,
    entry_time,
    entry_price,
    exit_time,
    exit_price,
    reason,
    direction,
    pip_size,
):
    """取引履歴の1行を作成する。"""
    # LONGでは値上がり、SHORTでは値下がりしたときにpipsがプラスになります。
    # USDJPYなどの円通貨は1pips=0.01円、それ以外は1pips=0.0001です。
    return {
        "取引番号": number,
        "エントリー日時": entry_time.isoformat(),
        "エントリー価格": round(entry_price, 6),
        "決済日時": exit_time.isoformat(),
        "決済価格": round(exit_price, 6),
        "決済理由": reason,
        # 足の本数 × 15分ではなく、日時の差から実際の経過時間を求めます。
        # これにより、土日やデータ欠損を挟んだ時間も保有時間に含まれます。
        "保有時間(分)": round(
            (exit_time - entry_time).total_seconds() / 60, 2
        ),
        "損益(pips)": round(
            direction * (exit_price - entry_price) / pip_size, 2
        ),
    }


def summarize(symbol, fast_period, slow_period, trades):
    """取引ごとのpips損益から成績をまとめる。"""
    profits = [trade["損益(pips)"] for trade in trades]

    # 0pipsの取引は勝ちにも負けにも数えませんが、取引回数には含めます。
    wins = [profit for profit in profits if profit > 0]
    losses = [profit for profit in profits if profit < 0]
    gross_profit = sum(wins)
    gross_loss = abs(sum(losses))
    # プロフィットファクター(PF)= 勝ち取引の総利益 / 負け取引の総損失です。
    # PFが1より大きければ、コストを考慮しない損益合計はプラスです。
    profit_factor = gross_profit / gross_loss if gross_loss else None

    # 累積損益の過去最高値から、どこまで下落したかを順番に調べます。
    # その最大値が最大ドローダウンです。
    equity = peak = max_drawdown = 0.0
    for profit in profits:
        equity += profit
        peak = max(peak, equity)
        max_drawdown = max(max_drawdown, peak - equity)

    trade_count = len(profits)
    return {
        "通貨ペア": symbol,
        "短期EMA期間": fast_period,
        "長期EMA期間": slow_period,
        "取引回数": trade_count,
        "勝ち取引数": len(wins),
        "負け取引数": len(losses),
        "勝率(%)": round(len(wins) / trade_count * 100, 2) if trade_count else 0.0,
        "合計損益(pips)": round(sum(profits), 2),
        "平均損益(pips)": round(sum(profits) / trade_count, 2) if trade_count else 0.0,
        "プロフィットファクター": round(profit_factor, 4) if profit_factor is not None else "",
        "最大ドローダウン(pips)": round(max_drawdown, 2),
    }


def run_grid(candles, symbol, fast_periods, slow_periods, output_dir):
    """1通貨についてEMA100パターンをLONG・SHORT別に検証する。"""
    if not candles:
        raise ValueError(f"{symbol}のローソク足データが空です")

    # 同じ期間のEMAを何度も計算しないよう、必要な期間を先にまとめます。
    all_periods = set(fast_periods) | set(slow_periods)
    closes = [candle["close"] for candle in candles]
    ema_by_period = {
        period: calculate_ema(closes, period) for period in all_periods
    }

    # EMA500の初期値はindex=499で確定する。前足と現在足を使うため
    # index=500から全設定を共通条件で評価する。
    evaluation_start_index = max(all_periods)
    # 円を決済通貨にする通貨ペアと、それ以外で1pipsの値が異なります。
    pip_size = 0.01 if symbol.endswith("_JPY") else 0.0001

    # LONGとSHORTは別フォルダ、別result.csvへ保存します。
    for side in ("LONG", "SHORT"):
        result_path = output_dir / symbol / side / "result.csv"
        result_path.parent.mkdir(parents=True, exist_ok=True)
        with result_path.open("w", encoding="utf-8-sig", newline="") as file:
            writer = csv.DictWriter(file, fieldnames=RESULT_COLUMNS)
            writer.writeheader()
            # 1設定ずつ検証・保存するため、全取引履歴を同時に保持しません。
            for fast_period in fast_periods:
                for slow_period in slow_periods:
                    if fast_period >= slow_period:
                        continue
                    trades = collect_trades(
                        candles,
                        ema_by_period[fast_period],
                        ema_by_period[slow_period],
                        side,
                        pip_size,
                        evaluation_start_index,
                    )
                    trade_path = (
                        output_dir
                        / symbol
                        / side
                        / "trades"
                        / f"EMA_{fast_period}_{slow_period}.csv"
                    )
                    write_csv(trade_path, trades, TRADE_COLUMNS)
                    writer.writerow(
                        summarize(symbol, fast_period, slow_period, trades)
                    )


def write_csv(path, rows, columns):
    """UTF-8 BOM付きCSVを保存する。"""
    # フォルダが存在しない初回実行時は、自動的に作成します。
    path.parent.mkdir(parents=True, exist_ok=True)

    # utf-8-sigはUTF-8にBOMを付け、Excelでの文字化けを起きにくくします。
    with path.open("w", encoding="utf-8-sig", newline="") as file:
        writer = csv.DictWriter(file, fieldnames=columns)
        writer.writeheader()
        writer.writerows(rows)


def main():
    """5通貨のEMAクロス検証を実行する。"""
    # スクリプト全体の実行時間を測定します。
    start_time = time.perf_counter()

    if BACKTEST_START_DATE > BACKTEST_END_DATE:
        raise SystemExit("検証開始日は検証終了日以前に設定してください")

    # 短期EMAが長期EMAより短い、有効な組み合わせ数を数えます。
    combinations = sum(
        fast < slow for fast in DEFAULT_FAST_PERIODS for slow in DEFAULT_SLOW_PERIODS
    )
    if combinations == 0:
        raise SystemExit("短期期間 < 長期期間となる組み合わせがありません")

    print(f"有効なEMA組み合わせ: {combinations}パターン")
    print(
        "検証期間: "
        f"{BACKTEST_START_DATE.isoformat()} ~ {BACKTEST_END_DATE.isoformat()}"
    )
    # 5通貨を1つずつ読み込み、各100パターンを検証します。
    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 = load_candles(path)
        if len(candles) <= max(DEFAULT_SLOW_PERIODS):
            raise SystemExit(
                f"{symbol}のデータが不足しています: "
                f"{len(candles)}本({max(DEFAULT_SLOW_PERIODS) + 1}本以上必要)"
            )
        print(f"[RUN] {symbol}: {ENTRY_TIMEFRAME}")
        run_grid(
            candles,
            symbol,
            DEFAULT_FAST_PERIODS,
            DEFAULT_SLOW_PERIODS,
            DEFAULT_OUTPUT_DIR,
        )

    elapsed_seconds = time.perf_counter() - start_time
    print(f"完了: {DEFAULT_OUTPUT_DIR}")
    print(f"実行時間: {elapsed_seconds:.2f}秒")
    return 0


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

実行コマンドは以下です。

python run_ema_backtest.py

検証結果は、通貨・LONG・SHORTごとにCSVへ保存されます。

CSVには、取引回数・勝率・合計損益・平均損益・PF・最大DDなどが保存されるため、EMA期間ごとの成績を比較できます。

次の章では、この検証結果をもとに、足切り条件と100点評価を通過したEMA設定を通貨ごとに紹介します。

通貨別に成績が良かったEMAの期間設定

ここでは、先ほど紹介した足切り条件と100点評価を通過したEMA設定を、通貨ごとに紹介します。

今回の評価では、単純な合計pipsだけではなく、PF・最大DD・利益/DD・直近6か月の成績・周辺設定の安定性なども含めて総合的に評価しています。

そのため、全期間の合計損益がプラスでも、直近6か月の成績や月別成績などの条件を満たさなかった設定は候補から除外しています。

なお、直近6か月は2026年2月1日~2026年7月31日とし、取引は決済日時を基準に各月へ集計しています。

今回、最終的に掲載対象となったのは以下の2グループです。

通貨LONGSHORT
USDJPYTOP5候補なし
EURJPY候補なし候補なし
GBPJPYTOP5候補なし
EURUSD候補なし候補なし
AUDUSD候補なし候補なし

USDJPYの検証結果

LONG

USDJPYのLONGでは、100設定中89設定が足切り条件を通過しました。

その中で、総合評価が高かったTOP5は以下のとおりです。

順位EMA評価点取引回数勝率全期間損益PF最大DD利益/DD直近6か月
145 / 7586.3130536.39%+2,756.9pips1.37311,896.3pips1.454+922.5pips
240 / 7584.4432438.27%+2,716.1pips1.35591,848.4pips1.469+906.1pips
350 / 6083.9533037.27%+2,705.7pips1.34361,879.7pips1.439+948.9pips
425 / 6082.2148134.10%+2,725.8pips1.29841,507.4pips1.808+830.3pips
545 / 6079.2035437.57%+2,580.1pips1.31571,933.0pips1.335+926.8pips

最も評価が高かったのは、短期EMA45・長期EMA75の組み合わせです。

全期間で+2,756.9pips、PF1.3731となっており、直近6か月でも+922.5pipsと好成績でした。

また、TOP5すべてで周辺安定性が100%となっています。

つまり、特定のEMA設定だけが突出して良かったのではなく、近い期間設定でも足切り条件を満たす結果が続いていたということです。

特に、短期EMA40~50・長期EMA60~75付近には好成績の設定が集中しています。

一方、EMA25/60は総合順位では4位ですが、利益/DDは1.808とTOP5の中で最も高く、最大DDとのバランスでは優秀な結果でした。

USDJPYのLONGは、今回の5通貨の中でも最も安定した結果となっています。

SHORT

USDJPYのSHORTでは、今回の足切り条件をすべて満たす設定はありませんでした。

全期間では合計損益がプラスとなる設定もありましたが、直近6か月の合計損益がプラスとなる設定はありませんでした。

特に直近6か月の合計pipsが足切り要因となり、最終的に条件を通過する設定は残りませんでした。

例えばEMA45/75では、直近6か月の月別損益は以下のとおりです。

損益
2026年2月+139.3pips
2026年3月-53.3pips
2026年4月-33.4pips
2026年5月+20.6pips
2026年6月-160.9pips
2026年7月+28.4pips
合計-59.3pips

そのため、今回の条件ではUSDJPYのSHORTに有効なEMA設定はなしと判断します。

EURJPYの検証結果

LONG

EURJPYのLONGでは、今回の足切り条件を満たす設定はありませんでした。

全期間では38設定がプラスとなり、そのうち15設定はPF1.05以上となっています。

また、直近6か月の合計損益がプラスとなった設定も8つありました。

しかし、全期間の成績・直近6か月の成績・月別成績など、すべての条件を同時に満たす設定は残りませんでした。

そのため、今回の評価基準ではEURJPYのLONGに有効なEMA設定は見つかりませんでした。

SHORT

EURJPYのSHORTでは、全検証期間の合計損益がプラスとなる設定はありませんでした。

一方、直近6か月では合計損益がプラスとなる設定が1つあり、3か月以上プラスとなった設定も複数確認できました。

ただし、全期間でプラスとなる設定がないため、今回の足切り条件を通過した設定はありません。

そのため、今回の条件ではEURJPYのSHORTに有効なEMA設定はなしとなりました。

GBPJPYの検証結果

LONG

GBPJPYのLONGでは、100設定中7設定が足切り条件を通過しました。

その中で、総合評価が高かったTOP5は以下のとおりです

順位EMA評価取引回数勝率全期間損益PF最大DD利益/DD直近6か月
15 / 20081.9257022.28%+1,468.9pips1.1381749.8pips1.959+331.2pips
210 / 20039.6241923.63%+866.4pips1.08541,083.4pips0.800+408.1pips
35 / 15034.1468722.71%+624.1pips1.05001,689.4pips0.369+517.8pips
45 / 12033.6878122.54%+845.8pips1.06391,312.7pips0.644+274.3pips
510 / 10031.8360727.18%+866.9pips1.06641,941.1pips0.447+433.7pips

最も評価が高かったのは、短期EMA5・長期EMA200の組み合わせです。

全期間では+1,468.9pips、PF1.1381、最大DD749.8pipsとなっており、利益/DDは1.959でした。

2位以下と比べても利益/DDが大きく、今回のGBPJPY LONGでは突出した結果となっています。

一方、2位以下は評価点が大きく下がっています。

EMA5/150は直近6か月で+517.8pips、周辺安定性80%と良好でしたが、全期間のPFは足切り条件の下限である1.05ちょうどでした。

このことから、GBPJPYのLONGでは複数の候補が見つかったものの、USDJPYほど広い範囲で安定しているわけではありません。

特にEMA5/200が今回の有力候補です。

SHORT

GBPJPYのSHORTでは、今回の足切り条件を満たす設定はありませんでした。

全検証期間および直近6か月の合計損益がプラスとなる設定はありませんでした。

月単位では利益を出した期間もありましたが、直近6か月の合計ではすべてマイナスとなっています。

そのため、全期間・直近6か月の両方を含む足切り条件を通過する設定はありませんでした。

今回の条件では、GBPJPYのSHORTに有効なEMA設定はなしとなります。

EURUSDの検証結果

LONG

EURUSDのLONGでは、今回の足切り条件を通過した設定はありませんでした。

全期間では9設定がプラスとなり、そのうち3設定はPF1.05以上となっています。

しかし、直近6か月の合計損益がプラスとなる設定はありませんでした。

つまり、過去の全期間だけを見ると利益が出ていた設定でも、最近の相場ではEMAクロスが機能していないケースがあったということです。

そのため、今回の評価基準ではEURUSDのLONGに有効な設定はありませんでした。

SHORT

EURUSDのSHORTでも、最終的な候補はありませんでした。

ただし、直近6か月だけを見るとSHORTは比較的良好でした。

直近6か月では78設定がプラスとなっており、3か月以上プラスとなった設定も96設定あります。

例えばEMA10/75では、直近6か月で+319.0pips、PF1.3101となっています。

一方、全検証期間では合計損益がプラスとなる設定が1つもありませんでした。

そのため、直近ではSHORTが良好でも、長期間の安定性まで含めると正式な候補には残りませんでした。

EURUSDは、全期間と直近6か月で有効な売買方向が大きく変化している点が特徴的な結果です。

AUDUSDの検証結果

LONG

AUDUSDのLONGでは、今回の足切り条件を満たす設定はありませんでした。

全期間で合計損益がプラスとなった設定は1つだけでしたが、その設定もPF1.05以上の条件を満たしていません。

一方、直近6か月では10設定がプラスとなっていました。

しかし、全期間のPF・直近成績・月別成績などを含む条件をすべて満たす設定はありませんでした。

そのため、今回の条件ではAUDUSDのLONGに有効なEMA設定はなしとなりました。

SHORT

AUDUSDのSHORTでも、足切り条件を通過する設定はありませんでした。

全期間で合計損益がプラスとなる設定は1つもありません。

直近6か月では7設定がプラスとなっており、3か月以上プラスとなった設定も33設定ありました。

ただし、全期間の成績まで含めて評価すると、安定した優位性は確認できませんでした。

今回の検証では、AUDUSDはLONG・SHORTともに有効なEMA設定を選出できない結果となっています。

今回の5通貨を比較すると、厳格な条件を通過したのはUSDJPY LONGとGBPJPY LONGのみでした。

特にUSDJPY LONGは89設定が足切り条件を通過し、TOP5すべてで周辺安定性100%となっています。

一方、GBPJPY LONGは7設定のみの通過で、EMA5/200が他の設定より大きく評価を伸ばす結果となりました。

このことから、今回のEMAクロス条件では、USDJPY LONGが最も安定しており、次いでGBPJPY LONGに有力候補が確認できたと考えられます。

EMAの検証結果を考察

今回の検証では、5通貨すべてで同じEMA設定が有効になるわけではなく、通貨や売買方向によって成績に大きな差が出ることが分かりました。

特に目立ったのは、正式な候補がLONGに集中した点です。

今回の足切り条件を通過したのは、

  • USDJPY LONG
  • GBPJPY LONG

の2グループのみでした。

USDJPY LONGでは100設定中89設定が条件を通過しており、特に短期EMA40~50・長期EMA60~75付近で好成績が集中しています。

  • USDJPY:EMA45/75、40/75、50/60、45/60など
  • GBPJPY:EMA5/200、10/200、5/150など

特にUSDJPYでは、上位5設定すべての周辺安定性が100%となっており、1つの設定だけが突出したのではなく、近いEMA期間でも安定した成績が続いている点が特徴です。

一方、GBPJPYではEMA5/200が最も高い評価となりましたが、2位以下とは評価点に大きな差があり、USDJPYほど広い範囲で安定しているわけではありませんでした。

また、全期間の合計pipsだけでは判断できないケースも確認できています。

例えばEURUSD LONGでは、全期間でプラスとなる設定がありましたが、直近6か月ではプラスとなる設定がありませんでした。

反対にEURUSD SHORTでは、直近6か月で好成績の設定が多かったものの、全期間ではプラスとなる設定がありませんでした。

この結果から、EMAの期間を選ぶ際は、過去の合計利益だけではなく、PF・最大DD・周辺設定の安定性・直近の成績まで確認することが重要だと分かります。

今回の検証では、すべての通貨に共通する「最適なEMA期間」は見つかりませんでした。

ただし、USDJPY LONGのように複数の近い設定で安定した成績が確認できるケースもあり、通貨と売買方向を分けて検証することで、有力なEMA期間を絞り込めることが分かりました。

EMAをFXで使う際の注意点

今回の検証結果を実際のトレードへ活用する際は、いくつか注意しておきたいポイントがあります。

  • 最適なEMA期間は通貨や売買方向によって異なる
    今回の検証でも、USDJPY LONGでは多くの設定が好成績だった一方、EURUSDやAUDUSDでは正式な候補が残りませんでした。同じEMA期間でも、通貨やLONG・SHORTによって結果は大きく変わります。
  • 過去に好成績でも将来の利益は保証されない
    全期間では利益が出ていても、直近6か月では成績が悪化した設定がありました。特にEURUSDでは、全期間と直近6か月でLONG・SHORTの傾向が大きく変わっています。
  • 1つだけ突出したEMA設定には注意する
    特定の期間だけが好成績でも、周辺のEMA設定が悪ければ偶然その設定だけが良かった可能性があります。1つの結果だけではなく、近い期間設定でも成績が維持されているか確認することが重要です。
  • 今回の検証結果は設定した期間の中での比較になる
    今回は短期EMA5~50、長期EMA60~500の100通りを比較しています。そのため、例えばEMA45/75が1位でも、EMA45/70やEMA50/65など、今回試していない設定の方が良い可能性もあります。
  • 実際の取引では成績が悪化する可能性がある
    今回はスプレッド・スリッページ・手数料・スワップを考慮していません。PFが1に近い設定や平均利益が小さい設定は、取引コストを加えることで利益が残らなくなる可能性があります。

今回の結果は「このEMA期間を使えば必ず勝てる」というものではなく、今回設定した条件の中で比較的成績が良かったEMA期間を確認したものです。

実際にEMAを使う際は、通貨や売買方向ごとに検証し、全期間の利益だけではなく、PF・最大DD・直近成績・周辺設定の安定性まで確認するようにしましょう。

まとめ

今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、短期EMA10種類×長期EMA10種類の100通りをLONG・SHORTそれぞれで検証しました。

その結果、今回の足切り条件を通過したのは、USDJPY LONGとGBPJPY LONGのみでした。

特に有力だったのは、

  • USDJPY LONG:EMA45/75
  • USDJPY LONG:EMA40/75
  • USDJPY LONG:EMA50/60
  • USDJPY LONG:EMA25/60
  • GBPJPY LONG:EMA5/200

などです。

中でもUSDJPY LONGは100設定中89設定が条件を通過し、上位5設定すべてで周辺安定性100%となりました。

一方、EURJPY・EURUSD・AUDUSDでは、今回の厳格な条件を満たすEMA設定はありませんでした。

また、全期間では利益が出ていても直近6か月では成績が悪化している設定もあり、合計pipsだけでEMA期間を決めるのは不十分だと分かります。

EMAの期間を選ぶ際は、

  • PF
  • 最大DD
  • 利益/DD
  • 直近6か月の成績
  • 周辺設定の安定性

なども含めて確認することが重要です。

今回の検証では、すべての通貨に共通する「最適なEMA期間」は見つかりませんでした。

ただし、USDJPY LONGのように複数の近い設定で安定した結果が確認できるケースもあります。

EMAを使う際は、一般的によく使われる期間だけをそのまま採用するのではなく、通貨や売買方向ごとに実際のデータで検証し、自分の取引条件に合った期間を選ぶようにしましょう。

-FX / EA, Python