FX / EA Python

FXのRSIで最適な期間・設定値は?5通貨・1,800ケースで検証

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

FXでRSIを使う際、

  • RSIの期間は14でいいの?
  • 70/30が本当におすすめなの?
  • 通貨によって設定は変えた方がいいの?

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

RSIでは期間14・70/30が定番設定としてよく使われます。

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

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

検証するのは、

  • RSI期間6種類
  • RSI水準5種類
  • TP/SL 5種類
  • LONG・SHORT

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

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

この記事を読めば、RSI14・70/30が本当に使いやすい設定なのか、通貨ごとにどのような違いがあるのかが分かります。

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

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

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

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

同じようにRSIを検証してみたい方は、参考にしてみてください。

run_rsi_backtest.py
"""5通貨の15分足でRSI逆張りと固定TP/SLを検証する。

RSIはWilder方式で計算する。売られすぎ・買われすぎ圏から戻り、
LONGは陽線、SHORTは陰線になった足でシグナルを確定する。
次の15分足の始値で約定し、固定pipsのTPまたはSLで決済する。
"""

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/relative_strength_index/run_rsi_backtest.py
# 3. 結果は、このファイルと同じフォルダにあるresultsへ保存されます。

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

# RSIの計算、シグナル判定、売買には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)

# RSI期間6種類と、買われすぎ・売られすぎ水準5種類を組み合わせます。
# 6 × 5 = 30パターンを、LONG・SHORTに分けて検証します。
RSI_PERIODS = (5, 7, 9, 14, 21, 28)
# 各要素は「(買われすぎ水準, 売られすぎ水準)」です。
RSI_LEVELS = ((60, 40), (65, 35), (70, 30), (75, 25), (80, 20))

# 検証する「(TP, SL)」の固定pipsペアです。
# 事前検証でLONGの成績と通貨間の安定性が良かった5組に絞ります。
TP_SL_PAIRS = (
    (100, 10),
    (100, 30),
    (100, 50),
    (150, 30),
    (150, 50),
)

# __file__は、このPythonファイル自身の場所を表します。
# どのフォルダから実行しても、同じ入力・出力先を参照できます。
SCRIPT_DIR = Path(__file__).resolve().parent
GMO_API_DIR = SCRIPT_DIR.parents[1]
DEFAULT_DATA_DIR = GMO_API_DIR / "data" / "candles"
# 検証結果は、このスクリプトと同じフォルダのresultsへ保存します。
DEFAULT_OUTPUT_DIR = SCRIPT_DIR / "results"

# RSI設定ごとの集計結果に保存する列です。
RESULT_COLUMNS = (
    "通貨ペア", "売買方向", "RSI期間", "買われすぎ", "売られすぎ",
    "TP(pips)", "SL(pips)",
    "取引回数", "勝ち取引数", "負け取引数", "勝率(%)",
    "合計損益(pips)", "平均損益(pips)", "プロフィットファクター",
    "最大ドローダウン(pips)", "平均保有時間(分)",
)

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

    # RSI用の終値、約定用の始値、SL判定用の高値・安値を読み込みます。
    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 BACKTEST_START_DATE <= timestamp.date() <= BACKTEST_END_DATE:
                candles.append({
                    "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"]),
                })
    return candles


def calculate_rsi(closes, period):
    """Wilderの平滑化(初期値はperiod本の平均)でRSIを計算する。"""
    if period <= 0:
        raise ValueError("RSI期間は1以上である必要があります")
    # RSIをまだ計算できない位置にはNoneを入れます。
    # RSI14には14本分の価格変化が必要なので、最初の14要素はNoneです。
    values = [None] * len(closes)
    if len(closes) <= period:
        return values

    # 最初のRSIは、直近period本の上昇幅と下落幅の単純平均から作ります。
    losses = []
    gains = []
    for index in range(1, period + 1):
        change = closes[index] - closes[index - 1]
        gains.append(max(change, 0.0))
        losses.append(max(-change, 0.0))
    average_gain = sum(gains) / period
    average_loss = sum(losses) / period
    values[period] = rsi_value(average_gain, average_loss)

    # 2つ目以降はWilderの平滑化を使います。
    # 平均上昇幅 = (前回平均 × (期間 - 1) + 今回上昇幅) / 期間
    for index in range(period + 1, len(closes)):
        change = closes[index] - closes[index - 1]
        gain = max(change, 0.0)
        loss = max(-change, 0.0)
        average_gain = (average_gain * (period - 1) + gain) / period
        average_loss = (average_loss * (period - 1) + loss) / period
        values[index] = rsi_value(average_gain, average_loss)
    return values


def rsi_value(average_gain, average_loss):
    """平均上昇幅と平均下落幅を0~100のRSIへ変換する。"""
    # 下落が一度もなければRSIは100です。値動きが全くなければ中立の50とします。
    if average_loss == 0:
        return 100.0 if average_gain > 0 else 50.0
    # 上昇が一度もなければRSIは0です。
    if average_gain == 0:
        return 0.0

    # RSI = 100 - 100 / (1 + 平均上昇幅 / 平均下落幅)
    relative_strength = average_gain / average_loss
    return 100.0 - 100.0 / (1.0 + relative_strength)


def collect_trades(
    candles, rsi, side, upper, lower, take_profit_pips, stop_loss_pips,
    pip_size, start_index,
):
    """RSIの圏外復帰と反発足を確認し、固定TPまたはSLで決済する。"""
    if side not in {"LONG", "SHORT"}:
        raise ValueError("sideはLONGまたはSHORTである必要があります")

    # positionがNoneなら、ポジションを持っていない状態です。
    # pendingには、現在足で確定した注文を次足始値で実行するためのRSIを保存します。
    position = None
    pending_entry_rsi = None
    trades = []
    for index in range(max(1, start_index), len(candles)):
        # 前の15分足でエントリー条件が確定していれば、現在足の始値で入ります。
        # シグナル足の終値で約定させないことで、未来参照を防ぎます。
        if position is None and pending_entry_rsi is not None:
            position = {
                "time": candles[index]["open_time"],
                "price": candles[index]["open"],
                "rsi": pending_entry_rsi,
            }
        pending_entry_rsi = None

        # ポジション保有中は、現在足の高値・安値でTP/SL到達を確認します。
        if position is not None:
            take_profit_distance = take_profit_pips * pip_size
            stop_distance = stop_loss_pips * pip_size
            if side == "LONG":
                take_profit_price = position["price"] + take_profit_distance
                stop_price = position["price"] - stop_distance
                take_profit_hit = candles[index]["high"] >= take_profit_price
                stop_hit = candles[index]["low"] <= stop_price
            else:
                take_profit_price = position["price"] - take_profit_distance
                stop_price = position["price"] + stop_distance
                take_profit_hit = candles[index]["low"] <= take_profit_price
                stop_hit = candles[index]["high"] >= stop_price

            # 15分足だけでは足の中でTPとSLのどちらへ先に達したか分かりません。
            # 同じ足で両方へ達した場合は、成績を楽観視しないようSLを優先します。
            if stop_hit:
                # SLはエントリー時に指定済みとし、指定SL価格で決済します。
                exit_price = stop_price
                exit_reason = f"SL {stop_loss_pips}pips"
            elif take_profit_hit:
                # TPはエントリー時に指定済みとし、指定TP価格で決済します。
                exit_price = take_profit_price
                exit_reason = f"TP {take_profit_pips}pips"
            else:
                exit_price = None
                exit_reason = None

            if exit_price is not None:
                trades.append(make_trade(
                    len(trades) + 1, position, candles[index]["close_time"],
                    exit_price, rsi[index], exit_reason, side, pip_size,
                ))
                position = None

        # 前足と現在足のRSIを比較し、水準を跨いだ瞬間だけシグナルにします。
        current = rsi[index]
        previous = rsi[index - 1]
        if previous is None or current is None:
            continue

        if position is None:
            # LONGはRSIが売られすぎ圏から戻り、同じ足が陽線なら
            # 上方向への反発を確認できたものとして次足の注文を予約します。
            if (
                side == "LONG"
                and previous <= lower < current
                and candles[index]["close"] > candles[index]["open"]
            ):
                pending_entry_rsi = current
            # SHORTはRSIが買われすぎ圏から戻り、同じ足が陰線なら
            # 下方向への反落を確認できたものとして次足の注文を予約します。
            elif (
                side == "SHORT"
                and previous >= upper > current
                and candles[index]["close"] < candles[index]["open"]
            ):
                pending_entry_rsi = current

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


def make_trade(number, position, exit_time, exit_price, exit_rsi, reason, side, pip_size):
    """取引履歴の1行を作成する。"""
    # LONGでは値上がり、SHORTでは値下がりしたときにpipsがプラスになります。
    direction = 1.0 if side == "LONG" else -1.0
    return {
        "取引番号": number,
        "エントリー日時": position["time"].isoformat(),
        "エントリー価格": round(position["price"], 6),
        "エントリー時RSI": round(position["rsi"], 4),
        "決済日時": exit_time.isoformat(),
        "決済価格": round(exit_price, 6),
        "決済時RSI": round(exit_rsi, 4) if exit_rsi is not None else "",
        "決済理由": reason,
        # 日時の差を使うため、土日やデータ欠損を挟んだ時間も含みます。
        "保有時間(分)": round((exit_time - position["time"]).total_seconds() / 60, 2),
        # 円通貨は1pips=0.01円、それ以外は1pips=0.0001として換算します。
        "損益(pips)": round(direction * (exit_price - position["price"]) / pip_size, 2),
    }


def summarize(
    symbol, side, period, upper, lower, take_profit_pips, stop_loss_pips, trades
):
    """取引ごとのpips損益からRSI設定の成績をまとめる。"""
    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]
    # PF = 勝ち取引の総利益 / 負け取引の総損失です。
    gross_loss = abs(sum(losses))

    # 累積損益の過去最高値からの最大下落幅を求めます。
    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)
    durations = [trade["保有時間(分)"] for trade in trades]
    return {
        "通貨ペア": symbol, "売買方向": side, "RSI期間": period,
        "買われすぎ": upper, "売られすぎ": lower, "取引回数": count,
        "TP(pips)": take_profit_pips,
        "SL(pips)": stop_loss_pips,
        "勝ち取引数": 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,
    }


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 run_symbol(candles, symbol, output_dir):
    """1通貨についてRSIとTP/SLの150パターンを売買方向別に検証する。"""
    # 同じ期間のRSIを設定ごとに再計算しないよう、最初にまとめて作ります。
    closes = [candle["close"] for candle in candles]
    rsi_by_period = {period: calculate_rsi(closes, period) for period in RSI_PERIODS}
    # 円を決済通貨にする通貨ペアと、それ以外で1pipsの値が異なります。
    pip_size = 0.01 if symbol.endswith("_JPY") else 0.0001
    rows = []
    # LONGとSHORTは別フォルダ、別result.csvへ保存します。
    for side in ("LONG", "SHORT"):
        for period in RSI_PERIODS:
            for upper, lower in RSI_LEVELS:
                for take_profit_pips, stop_loss_pips in TP_SL_PAIRS:
                    trades = collect_trades(
                        candles, rsi_by_period[period], side, upper, lower,
                        take_profit_pips, stop_loss_pips, pip_size,
                        max(RSI_PERIODS) + 1,
                    )
                    # ファイル数を抑えるため、設定ごとの個別取引CSVは保存しません。
                    # 取引履歴は集計が終わると破棄し、成績だけresult.csvへ残します。
                    rows.append(summarize(
                        symbol, side, period, upper, lower,
                        take_profit_pips, stop_loss_pips, trades,
                    ))
        write_csv(
            output_dir / symbol / side / "result.csv",
            [row for row in rows if row["売買方向"] == side], RESULT_COLUMNS,
        )
    return rows


def main():
    """5通貨のRSI逆張り設定を一括検証する。"""
    # スクリプト全体の実行時間を測定します。
    started = time.perf_counter()
    if BACKTEST_START_DATE > BACKTEST_END_DATE:
        raise SystemExit("検証開始日は検証終了日以前に設定してください")
    # 1通貨30設定 × TP/SL 5組 × LONG・SHORT × 5通貨 = 1,500行です。
    # 出力は通貨・方向別result.csv 10個とall_results.csv 1個の計11個です。
    all_rows = []
    combinations = len(RSI_PERIODS) * len(RSI_LEVELS) * len(TP_SL_PAIRS)
    print(f"RSI・TP/SL設定: {combinations}パターン × LONG/SHORT")
    print(f"検証期間: {BACKTEST_START_DATE} ~ {BACKTEST_END_DATE}")
    # 5通貨を1つずつ読み込み、各設定を検証します。
    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(RSI_PERIODS) + 1:
            raise SystemExit(f"{symbol}のデータが不足しています: {len(candles)}本")
        print(f"[RUN] {symbol}: {len(candles)}本")
        all_rows.extend(run_symbol(candles, symbol, DEFAULT_OUTPUT_DIR))
    # 通貨別result.csvに加え、全結果を比較できるCSVも保存します。
    write_csv(DEFAULT_OUTPUT_DIR / "all_results.csv", all_rows, RESULT_COLUMNS)
    print(f"完了: {DEFAULT_OUTPUT_DIR}")
    print(f"実行時間: {time.perf_counter() - started:.2f}秒")
    return 0


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

FXのRSIとは

RSI(Relative Strength Index)は、一定期間の値動きから、買いと売りのどちらの勢いが強いかを0〜100の数値で表すテクニカル指標です。

日本語では「相対力指数」と呼ばれます。

RSIを見ることで、

  • 買われすぎているのか
  • 売られすぎているのか
  • 買いと売りの勢いがどちらに傾いているのか

を判断しやすくなります。

特にFXでは、価格が大きく上昇・下落したあとに、反発や反落のタイミングを探すための指標としてよく使われます。

RSIで分かること

RSIは0〜100の範囲で表示されます。

基本的な見方は、以下の通りです。

  • RSIが高い → 買いの勢いが強い
  • RSIが低い → 売りの勢いが強い
  • RSIが50付近 → 買いと売りの勢いが比較的均衡している

たとえばRSIが70を超えている場合は、価格が短期間に大きく上昇しており、買われすぎの状態と判断されることがあります。

反対に、RSIが30を下回っている場合は、価格が短期間に大きく下落しており、売られすぎの状態と判断されることがあります。

ただし、RSIが70を超えたからすぐに下落する、30を下回ったからすぐに上昇する、というわけではありません。

強いトレンドが続いている場合は、RSIが高い・低い状態のまま推移することもあります。

RSIでよく使われる期間

RSIでは、直近何期間の値動きを使って計算するかを設定します。

一般的によく使われるのが、RSI14です。

ここでいう14期間は、14日という意味ではありません。

使用している時間足のローソク足14本分を表します。

たとえば15分足でRSI14を使用する場合は「15分足 × 14本 = 210分」となるため、約3時間30分の値動きをもとにRSIを計算します。

同じRSI14でも、時間足によって対象となる期間は変わります。

  • 15分足 → 約3時間30分
  • 1時間足 → 14時間
  • 4時間足 → 56時間
  • 日足 → 14日

また、RSIの期間を短くすると直近の値動きに敏感になり、長くすると値動きがなめらかになります。

そのため、RSI14が必ず最適とは限らず、通貨やトレード方法によって適した期間が異なる可能性があります。

買われすぎ・売られすぎの設定値

RSIでは、期間とあわせて買われすぎ・売られすぎを判断する水準も重要です。

一般的によく使われる設定は、以下です。

  • 買われすぎ:70以上
  • 売られすぎ:30以下

たとえば、

  • RSI70以上 → 買われすぎ
  • RSI30以下 → 売られすぎ

と判断します。

ただし、70/30以外にも、

  • 60/40
  • 65/35
  • 75/25
  • 80/20

などの設定が考えられます。

60/40のように基準を浅くするとシグナルは増えやすく、80/20のように基準を厳しくするとシグナルは少なくなります。

つまり、RSIでは「期間」と「買われすぎ・売られすぎの設定値」の組み合わせによって、エントリーの回数や成績が大きく変わる可能性があります。

そこで本記事では、一般的なRSI14・70/30が本当に最適なのかを確認するため、複数のRSI期間と設定値を組み合わせて検証していきます。

今回のRSI検証条件

ここからは、RSIの期間や設定値によって成績がどのように変わるのか、実際の15分足データを使って検証します。

今回はRSIだけでなく、TP・SLも組み合わせて合計1,500ケースをバックテストします。

検証する5通貨・時間足・期間

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

  • USDJPY
  • EURJPY
  • GBPJPY
  • EURUSD
  • AUDUSD

すべて15分足を使用し、検証期間は2024年1月1日〜2026年7月31日とします。

価格データにはASKを使用し、スプレッドやスリッページなどの取引コストは今回の検証には含めません

検証するRSI期間と水準

RSI期間は、以下の6種類を比較します。

  • 5・7・9・14・21・28

また、買われすぎ・売られすぎの水準は以下の5種類です。

買われすぎ売られすぎ
6040
6535
7030
7525
8020

一般的なRSI14・70/30だけでなく、期間を短くした場合や、水準を浅く・深くした場合も比較します。

検証するTP・SL

今回は、エントリー後の決済に固定TP・SLを使用します。

検証する組み合わせは以下の5種類です。

TPSL
100pips10pips
100pips30pips
100pips50pips
150pips30pips
150pips50pips

これらは事前検証を行い、LONGで比較的成績が良く、複数通貨でも安定していた組み合わせから選んでいます。

LONG・SHORTのエントリー条件

今回は、RSIが買われすぎ・売られすぎの水準に到達しただけではエントリーしません。

水準から戻ったことに加えて、ローソク足の方向も確認します。

LONGの条件は以下です。

LONGの条件

  • 前の15分足のRSIが売られすぎ水準以下
  • 現在のRSIが売られすぎ水準を上抜ける
  • 現在の15分足が陽線
  • 次の15分足の始値でLONG

SHORTの条件

  • 前の15分足のRSIが買われすぎ水準以上
  • 現在のRSIが買われすぎ水準を下抜ける
  • 現在の15分足が陰線
  • 次の15分足の始値でSHORT

シグナルが確定した足ではなく、次の15分足の始値でエントリーすることで未来参照を避けています。

TP・SLによる決済条件

エントリーと同時に、あらかじめ決めたTP・SLを設定したものとして検証します。

LONGでは、

  • 価格がTPまで上昇 → 利確
  • 価格がSLまで下落 → 損切り

SHORTでは反対に、

  • 価格がTPまで下落 → 利確
  • 価格がSLまで上昇 → 損切り

とします。

また、同じ15分足の中でTPとSLの両方に到達した場合は、どちらが先だったのか15分足だけでは判断できません。

そのため今回は、成績を良く見せすぎないようにSLを優先します。

合計1,500ケースを検証

今回の組み合わせは、

  • 5通貨 × LONG・SHORT × RSI期間6種類 × RSI水準5種類 × TP/SL5種類

です。

つまり、

  • 5 × 2 × 6 × 5 × 5 = 1,500ケース

を検証します。

設定を1つだけ見て判断するのではなく、多数の組み合わせを比較することで、どのRSI期間・水準・TP・SLが安定しているのかを確認します。

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

1,500ケースの中から成績が良かった設定を選ぶ際は、合計pipsだけでは判断しません。

一部の期間だけ偶然利益が大きかった設定や、取引回数が少なすぎる設定を避けるため、

  • 合計pips
  • プロフィットファクター(PF)
  • 最大ドローダウン
  • 1取引あたりの平均損益
  • 取引回数

などを確認しながら比較します。

特に、利益が大きくても最大ドローダウンが極端に大きい設定や、少ない取引だけで高成績になっている設定には注意します。

この基準をもとに、次の章では5通貨それぞれで成績が良かったRSI設定を確認していきます。

5通貨のRSI検証結果

ここからは、1,500ケースのバックテスト結果から、成績が良かったRSI設定を通貨別に確認します。

今回はLONG・SHORTを分けて評価し、合計pipsだけではなく、PF・最大ドローダウン・平均損益・取引回数などを含めて比較しています。

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

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

USDJPY LONG:TOP5

順位RSI期間水準TP/SL取引回数合計pipsPF最大DD
12860/40100/10970+3,610.01.4252350
2780/20150/50388+3,600.01.2637900
32160/40100/101,266+3,950.01.3543500
4780/20100/50487+3,550.01.2359900
5980/20150/30379+2,490.01.2748540

1位はRSI28・60/40・TP100・SL10でした。

970回の取引で+3,610pips、PF1.4252、最大DD350pipsとなっており、利益とドローダウンのバランスが特に良い結果です。

また、TOP5には60/40だけでなく80/20も複数入っており、USDJPYではRSI水準によって異なるタイプの有力設定が見つかりました。

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

USDJPY SHORTでは、今回の選定基準をすべて満たす設定はありませんでした。

一部にプラスとなる設定はありましたが、PFや最大ドローダウンまで含めて評価すると、LONGほど安定した結果にはなっていません。

そのため、USDJPYでは今回のRSI逆張りルールはLONGの方が相性が良かったと考えられます。

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

EURJPY LONG:TOP5

順位RSI期間水準TP/SL取引回数合計pipsPF最大DD
12160/40100/30728+4,940.01.3155930
21470/30100/30545+3,965.21.3407850
3780/20100/30558+3,575.21.2972800
4975/25100/30601+3,715.21.2860840
5975/25100/10852+3,155.21.4235470

1位はRSI21・60/40・TP100・SL30です。

728回の取引で+4,940pips、PF1.3155となりました。

一方で、RSI14・70/30やRSI7・80/20なども上位に入っており、特定のRSI期間だけに偏っていないのが特徴です。

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

EURJPY SHORTでは、今回の基準をすべて満たす設定はありませんでした。

LONGでは多くの有力設定が見つかった一方、SHORTでは同じRSI逆張りルールの優位性を確認できませんでした。

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

GBPJPY LONG:TOP5

順位RSI期間水準TP/SL取引回数合計pipsPF最大DD
12860/40150/30677+6,000.81.3774720
22860/40150/50497+5,400.81.31311,000
32160/40100/50664+5,250.81.2580700
4980/20100/50380+4,000.81.3541800
5980/20150/30400+3,690.81.3943750

1位はRSI28・60/40・TP150・SL30です。

677回の取引で+6,000.8pips、PF1.3774、最大DD720pipsとなりました。

上位にはRSI28・60/40が2つ入っており、GBPJPYでは比較的長めのRSI期間も有力でした。

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

GBPJPY SHORTにもプラスとなる設定はありましたが、今回の厳しい選定基準をすべて満たす設定はありませんでした。

GBPJPYについても、今回のロジックではLONG側の成績が明確に優勢です。

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

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

順位RSI期間水準TP/SL取引回数合計pipsPF最大DD
1580/20150/30259+1,099.41.1753540
21470/30150/30218+1,020.01.1932510
3580/20100/50239+1,250.01.1656650
4775/25150/30271+919.41.1393600

1位はRSI5・80/20・TP150・SL30でした。

259回の取引で+1,099.4pips、PF1.1753、最大DD540pipsです。

また、一般的なRSI14・70/30も2位に入っており、EURUSDでは定番設定も比較的良い結果となりました。

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

EURUSD SHORTでは、今回の選定基準をすべて満たす設定はありませんでした。

EURUSDについても、今回のRSI逆張りではLONGの方が安定しています。

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

AUDUSD LONG:TOP5

順位RSI期間水準TP/SL取引回数合計pipsPF最大DD
12865/35100/10343+860.01.2829350
21470/30100/10489+859.51.1971270
32170/30100/10266+530.01.2236230
42165/35100/10511+859.51.1885470
5970/30100/10657+1,049.51.1788580

1位はRSI28・65/35・TP100・SL10でした。

343回の取引で+860pips、PF1.2829、最大DD350pipsとなっています。

AUDUSD LONGのTOP5では、すべてTP100・SL10が選ばれました。

RSI期間や水準よりも、TP/SLの組み合わせに共通点が見られる結果です。

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

順位RSI期間水準TP/SL取引回数合計pipsPF最大DD
1980/20100/10308+880.01.3235290
21470/30100/10446+1,150.01.2911560

AUDUSD SHORTでは2設定だけが基準を満たしました。

1位のRSI9・80/20・TP100・SL10は、308回の取引で+880pips、PF1.3235、最大DD290pipsとなっています。

SHORT全体では成績が弱かったものの、AUDUSDについては一部に有力な設定が残りました。

今回の結果からも、LONG・SHORTを同じ設定で考えるのではなく、売買方向ごとに分けて検証することが重要だと分かります。

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

5通貨の結果を比較すると、すべての通貨で共通する最適なRSI設定はないことが分かりました。

USDJPY・EURJPY・GBPJPYではRSI21や28のような長めの期間が上位に入る一方、EURUSDではRSI5、AUDUSDではRSI14や28なども有力でした。

また、一般的な70/30だけでなく、60/40・65/35・80/20といった水準も上位に入っています。

そのため、RSI14・70/30を基準にすることはできますが、期間や水準は通貨ごとに検証した方がよいと考えられます。

今回特に大きな違いが出たのがLONGとSHORTです。

LONGでは5通貨すべてに有力な設定が見つかりましたが、SHORTは基準を満たす設定が少なく、AUDUSDの一部を除いて安定した成績にはなりませんでした。

また、TP・SLにも通貨ごとの差があり、AUDUSDではSL10pips、GBPJPYではSL30〜50pipsの設定が上位に多くなっています。

今回の結果から、RSIを使う場合は、期間・水準・売買方向・TP/SLをセットで考え、通貨ごとに検証することが重要だと分かりました。

定番のRSI14・70/30は本当に優秀?

今回の検証では、RSI14・70/30はLONGで比較的安定した結果となりました。

一方で、通貨別の上位設定を見ると、RSI28・60/40やRSI5・80/20など、別の組み合わせが上位になるケースもあります。

そのため、RSI14・70/30は「最適設定」というより、比較の基準として使いやすい設定と考えるのがよさそうです。

RSIを使う際は、まず14・70/30を基準にしつつ、通貨ごとに期間や水準を変えて検証するのがおすすめです。

RSIの検証結果から分かったこと

今回の1,500ケースを比較すると、RSIは1つの定番設定だけで決めるより、通貨や売買方向に合わせて調整することが重要だと分かりました。

特に、期間・水準・TP・SLを組み合わせて見ることで、それぞれの通貨に合う設定の違いが見えてきます。

1つの設定だけで判断しない

RSIでは、1つの設定だけが突出して良いケースもあります。

しかし、その設定だけを見て「これが最適」と判断するのは危険です。

今回の検証でも、上位にはRSI14・70/30だけでなく、RSI28・60/40やRSI5・80/20など、さまざまな組み合わせが入りました。

そのため、1位だけを見るのではなく、近い期間や水準でも安定しているかを確認することが重要です。

高成績でも取引回数が少ない設定は慎重に見る

RSI水準を厳しくすると、1取引あたりの成績が良くなる一方で、取引回数が大きく減る場合があります。

今回の事前検証でも、85/15や90/10まで厳しくすると、取引回数が不足する設定が多くなりました。

PFや平均pipsが高くても、取引回数が少なすぎると偶然の影響を受けやすくなります。

そのため、成績の高さと取引回数の両方を見ることが重要です。

LONGとSHORTは分けて考える

今回の検証では、LONGとSHORTで成績に大きな差が出ました。

LONGでは5通貨すべてに有力な設定が見つかった一方で、SHORTは基準を満たす設定がかなり少ない結果となりました。

そのため、LONGで成績が良いRSI設定を、そのままSHORTにも使うのはおすすめできません。

売買方向ごとに別の戦略として検証することが重要です。

通貨ごとにRSI・TP・SLを調整する

今回の結果では、成績が良かったRSI期間や水準、TP・SLは通貨ごとに異なりました。

たとえば、長めのRSI期間が上位に入りやすい通貨もあれば、短めの期間が有力な通貨もあります。

TP・SLについても、狭いSLが良い通貨と、ある程度広いSLが良い通貨に分かれました。

そのため、RSIを使う際は「RSI期間・水準・TP・SLをセットで考え、通貨ごとに検証する」という考え方が重要です。

RSIをFXで使う際の注意点

今回の検証では、RSIを使った逆張りで有力な設定がいくつか見つかりました。

ただし、バックテストで成績が良かった設定でも、そのまま実運用で同じ結果になるとは限りません

RSIをFXで使う際は、以下の点に注意が必要です。

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

RSIが売られすぎ・買われすぎの水準に到達しても、すぐに反転するとは限りません。

強いトレンドが続いている場合は、RSIが低いままさらに下落したり、高いまま上昇したりすることもあります。

そのため今回の検証では、RSIの水準だけでなく、

  • LONGでは陽線
  • SHORTでは陰線

も確認してから、次の15分足でエントリーしています。

RSIだけで判断せず、実際の値動きもあわせて確認することが重要です。

逆張りでは損切り設定が重要

RSIを使った逆張りでは、想定した反発・反落が起きず、そのままトレンドが続くことがあります。

そのため、どこで損切りするのかを事前に決めておくことが重要です。

今回の検証でも、TPとSLをあらかじめ固定し「TP100〜150pips・SL10〜50pips」の複数パターンを比較しました。

RSI設定だけでなく、TP・SLまで含めて戦略として考える必要があります。

スプレッド・取引コストは今回の検証に含まない

今回のバックテストではASK価格を使用し、スプレッド・スリッページ・手数料などの取引コストは考慮していません。

そのため、実際の取引ではバックテスト結果より成績が悪くなる可能性があります

特に、1取引あたりの平均利益が小さい設定は、取引コストの影響を受けやすくなります。

実運用を考える場合は、取引コストを差し引いた状態でも利益が残るか確認することが重要です。

過去の検証結果が将来も続くとは限らない

バックテストで成績が良かった設定でも、将来の相場で同じように機能する保証はありません。

相場のボラティリティやトレンドの出方は、時期によって変化します。

また、今回の検証では2024年1月1日〜2026年7月31日のデータを使って設定を比較しているため、検証期間に最適化されている可能性もあります。

そのため、実際に使用する前には、期間を分けた検証や別期間での再検証を行い、特定の期間だけで成績が良くなっていないか確認することが重要です。

まとめ

今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、RSI期間・水準・TP/SL・LONG/SHORTを組み合わせた1,500ケースを検証しました。

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

  • RSI14・70/30は基準設定として使いやすい
  • ただし、通貨によって成績が良いRSI期間・水準は異なる
  • LONGは5通貨で比較的良い結果となった
  • SHORTは基準を満たす設定が少なかった
  • RSIだけでなく、TP・SLまで含めて検証することが重要
  • 1つの高成績な設定だけでなく、取引回数やPF、最大ドローダウンも確認する必要がある

今回の結果では、「RSI14・70/30を使えばよい」という単純な結論にはなりませんでした。

定番設定を基準にしつつ、通貨や売買方向に合わせてRSI期間・水準・TP/SLを調整することが重要です。

また、今回のバックテストではスプレッドやスリッページなどの取引コストを考慮しておらず、過去の成績が将来も続く保証はありません。

実際に使用する場合は、今回の結果をそのまま採用するのではなく、別期間での検証や取引コストを含めた確認を行ったうえで判断してください。

-FX / EA, Python