FX自動売買 Python

FXのストキャスティクスでおすすめの設定は?5通貨10,500ケースで検証

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

FXでストキャスティクスを使う際、

  • 期間は14でいいの?
  • 80・20を基準にすればいいの?
  • %Kや%Dはどのように設定すればいいの?

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

ストキャスティクスでは、14・3・3、買われすぎ80・売られすぎ20といった設定が一般的によく使われます。

ただし、すべての通貨ペアやLONG・SHORTで、この設定が最適とは限りません。

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

検証するのは、

  • %K期間7種類
  • %K平滑化期間5種類
  • %D期間5種類
  • 買われすぎ・売られすぎの基準値6種類
  • LONG・SHORT

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

エントリーは、LONGでは売られすぎ圏で%Kが%Dを上抜けた場合、SHORTでは買われすぎ圏で%Kが%Dを下抜けた場合とし、シグナル確定後の次の15分足始値でエントリーします。

また、ストキャスティクスの設定差を比較しやすくするため、決済条件はTP50pips・SL30pipsに固定しています。

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

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

先に結果を確認したい方は、「5通貨でストキャスティクス10,500ケースを検証」からご覧ください。

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

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

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

run_stochastic_backtest.py
"""5通貨の15分足でストキャスティクス10,500設定を比較する。

LONGは売られすぎ圏からの上抜け、SHORTは買われすぎ圏からの下抜けを
%Kと%Dのクロスで確認する。シグナル確定の次足始値で入り、固定TP/SLで
決済する。%K期間、基準値、%K平滑化期間、%D期間を比較する。
"""

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/stochastic_oscillator/run_stochastic_backtest.py
# 3. 結果は backtest_research/stochastic_oscillator/results に保存されます。

# ストキャスティクスの計算、シグナル判定、売買には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")

# Slow %K = SMA(Raw %K, %K平滑化期間)、%D = SMA(Slow %K, %D期間)
# %K期間は、現在の終値が直近何本の高値・安値のどこにあるかを調べる期間です。
STOCHASTIC_PERIODS = (5, 7, 14, 18, 21, 25, 28)

# Raw %Kを何本平均してSlow %Kにするかを比較します。
# 1の場合はRaw %Kを平均せず、そのままSlow %Kとして使います。
SLOW_K_SMOOTHINGS = (1, 2, 3, 4, 5)

# Slow %Kを何本平均して%Dにするかを比較します。
# 1では%Kと%Dが同じ値になりクロスが発生しないため、2以上にしています。
D_PERIODS = (2, 3, 4, 5, 6)

# 各要素は(買われすぎ, 売られすぎ)。上下は100から見て対称にする。
# 例として80/20なら、%Dが20以下でLONG、80以上でSHORTのクロスを探します。
STOCHASTIC_LEVELS = (
    (55, 45), (60, 40), (70, 30),
    (75, 25), (80, 20), (90, 10),
)

# ストキャスティクス設定を公平に比べるため、出口は全ケースで固定します。
TAKE_PROFIT_PIPS = 50
STOP_LOSS_PIPS = 30

# 最も計算に時間がかかる設定でも、検証開始時に%Kと%Dが揃う本数です。
# 最大%K期間28 + 最大%K平滑化5 + 最大%D期間6 - 2 = 37本です。
WARMUP_BARS = (
    max(STOCHASTIC_PERIODS) + max(SLOW_K_SMOOTHINGS) + max(D_PERIODS) - 2
)

# __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"

# 結果CSVへ出力する列と、その並び順です。
RESULT_COLUMNS = (
    "通貨ペア", "売買方向", "%K期間", "%K平滑化期間", "%D期間",
    "買われすぎ", "売られすぎ", "TP(pips)", "SL(pips)",
    "取引回数", "勝ち取引数", "負け取引数", "勝率(%)",
    "合計損益(pips)", "平均損益(pips)", "プロフィットファクター",
    "最大ドローダウン(pips)", "平均保有時間(分)", "最終足決済数",
)


def load_candles(path):
    """検証開始前のウォームアップ足と検証期間内のOHLCを読む。"""
    # deque(maxlen=37)は上限を超えると古い足を自動的に捨てます。
    # 検証開始前のデータは指標を準備するためだけに使い、売買には使いません。
    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の文字列を、計算に使えるdatetimeと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:
                warmup.append(candle)
            else:
                backtest.append(candle)
    return list(warmup) + backtest, len(warmup), len(backtest)


def simple_moving_average(values, period):
    """Noneを含む先頭区間を考慮して単純移動平均を返す。"""
    if period <= 0:
        raise ValueError("移動平均期間は1以上にしてください")
    # SMA(1)は入力値そのもの。加算と減算を繰り返す実装を通すと、
    # 浮動小数点誤差によって同一であるはずの%Kと%Dに偽クロスが生じる。
    if period == 1:
        return list(values)

    # まだ平均を計算できない先頭部分にはNoneを残します。
    # windowには直近period個の値、totalにはその合計を保持します。
    result = [None] * len(values)
    window = deque()
    total = 0.0
    for index, value in enumerate(values):
        if value is None:
            # Raw %Kなどをまだ計算できない区間では平均窓をリセットします。
            window.clear()
            total = 0.0
            continue
        window.append(value)
        total += value
        if len(window) > period:
            # 必要期間より古くなった値を窓と合計から取り除きます。
            total -= window.popleft()
        if len(window) == period:
            # 必要な本数が揃った位置から平均値を記録します。
            result[index] = total / period
    return result


def calculate_raw_percent_k(candles, period):
    """指定期間のRaw %Kを0~100で計算する。"""
    if period <= 0:
        raise ValueError("%K期間は1以上にしてください")
    raw_k = [None] * len(candles)
    for index in range(period - 1, len(candles)):
        # 現在足を含む直近period本から、最高値と最安値を探します。
        window = candles[index - period + 1:index + 1]
        highest = max(candle["high"] for candle in window)
        lowest = min(candle["low"] for candle in window)
        price_range = highest - lowest
        # 例:最安値100、最高値110、終値108なら、
        # (108 - 100) ÷ (110 - 100) × 100 = 80です。
        # 全足が同値なら値幅が0になるため、中立値の50としてゼロ除算を避けます。
        raw_k[index] = (
            50.0 if price_range == 0
            else (candles[index]["close"] - lowest) / price_range * 100.0
        )
    return raw_k


def calculate_stochastic(candles, period, k_smoothing=3, d_period=3):
    """Slow %Kと%Dを0~100で計算する。"""
    if k_smoothing <= 0 or d_period <= 0:
        raise ValueError("ストキャスティクスの各期間は1以上にしてください")
    # Raw %Kをk_smoothing本平均したものがSlow %Kです。
    # さらにSlow %Kをd_period本平均したものが%Dです。
    raw_k = calculate_raw_percent_k(candles, period)
    slow_k = simple_moving_average(raw_k, k_smoothing)
    percent_d = simple_moving_average(slow_k, d_period)
    return slow_k, percent_d


def make_trade(position, candle, exit_price, reason, side, pip_size):
    """決済済み取引を作る。"""
    # LONGは値上がり、SHORTは値下がりしたときに損益がプラスになります。
    direction = 1.0 if side == "LONG" else -1.0
    return {
        # USDJPYを150.00円で買って150.50円で決済した場合は、
        # (150.50 - 150.00) ÷ 0.01 = 50pipsです。
        "損益(pips)": direction * (exit_price - position["price"]) / pip_size,
        "保有時間(分)": (
            candle["close_time"] - position["time"]
        ).total_seconds() / 60,
        "決済理由": reason,
    }


def collect_trades(candles, slow_k, percent_d, side, upper, lower, pip_size,
                   entry_start_index):
    """現在%Dがゾーン内にあるK/Dクロスを次足始値で約定させる。"""
    if side not in SIDES:
        raise ValueError("sideはLONGまたはSHORTにしてください")
    # 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 = TAKE_PROFIT_PIPS * pip_size
            sl_distance = STOP_LOSS_PIPS * 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
            # 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

        values = slow_k[index - 1], percent_d[index - 1], slow_k[index], percent_d[index]
        # ポジション保有中や、指標をまだ計算できない位置では判定しません。
        if position is not None or any(value is None for value in values):
            continue
        previous_k, previous_d, current_k, current_d = values
        if side == "LONG":
            # LONGは%Kが%Dを下から上へ抜けた瞬間を探します。
            crossed = previous_k <= previous_d and current_k > current_d
            in_extreme_zone = current_d <= lower
        else:
            # SHORTは%Kが%Dを上から下へ抜けた瞬間を探します。
            crossed = previous_k >= previous_d and current_k < current_d
            in_extreme_zone = current_d >= upper

        # ここではまだ約定せず、次のループで次足始値を使います。
        if crossed and in_extreme_zone and index >= entry_start_index:
            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, k_smoothing, d_period, upper, lower,
              trades):
    """1設定の取引成績を集計する。"""
    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]
    # 取引順の累積損益について、過去最高値からの最大下落幅を求めます。
    # 累積損益が[100, 250, 180, 50]なら、最大DDは250 - 50 = 200pipsです。
    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, "%K期間": period,
        "%K平滑化期間": k_smoothing, "%D期間": d_period,
        "買われすぎ": upper, "売られすぎ": lower,
        "TP(pips)": TAKE_PROFIT_PIPS, "SL(pips)": STOP_LOSS_PIPS,
        "取引回数": 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):
    """Excelでも開きやすい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=RESULT_COLUMNS)
        writer.writeheader()
        writer.writerows(rows)


def run_symbol(candles, symbol, entry_start_index, output_dir):
    """1通貨について2,100ケースを検証する。"""
    # 円を決済通貨にする通貨ペアは1pips=0.01円、それ以外は0.0001です。
    pip_size = 0.01 if symbol.endswith("_JPY") else 0.0001

    # LONGとSHORTを別々のCSVへ保存するため、方向別に結果を貯めます。
    rows_by_side = {side: [] for side in SIDES}

    # Raw %Kは%K期間ごとに1回だけ計算する。指標175組を同時に保持せず、
    # 1組ずつ検証して破棄することでメモリ使用量を抑える。
    for period in STOCHASTIC_PERIODS:
        raw_k = calculate_raw_percent_k(candles, period)
        for k_smoothing in SLOW_K_SMOOTHINGS:
            slow_k = simple_moving_average(raw_k, k_smoothing)
            for d_period in D_PERIODS:
                percent_d = simple_moving_average(slow_k, d_period)
                for side in SIDES:
                    for upper, lower in STOCHASTIC_LEVELS:
                        # 1つの「期間・平滑化・%D・基準値・方向」を検証します。
                        trades = collect_trades(
                            candles, slow_k, percent_d, side, upper, lower,
                            pip_size, entry_start_index,
                        )
                        rows_by_side[side].append(summarize(
                            symbol, side, period, k_smoothing, d_period,
                            upper, lower, trades,
                        ))

    rows = []
    for side in SIDES:
        side_rows = rows_by_side[side]
        write_csv(output_dir / symbol / side / "result.csv", side_rows)
        rows.extend(side_rows)
    return rows


def main():
    """5通貨・2方向のストキャスティクス10,500ケースを検証する。"""
    # スクリプト全体の実行時間を測り始めます。
    started = time.perf_counter()
    if BACKTEST_START_DATE > BACKTEST_END_DATE:
        raise SystemExit("検証開始日は検証終了日以前に設定してください")
    if TAKE_PROFIT_PIPS <= 0 or STOP_LOSS_PIPS <= 0:
        raise SystemExit("TPとSLは1pips以上にしてください")
    # 定数を変更しても表示が追従するよう、実際の候補数からケース数を計算します。
    total_cases = (
        len(SYMBOLS) * len(SIDES) * len(STOCHASTIC_PERIODS)
        * len(STOCHASTIC_LEVELS) * len(SLOW_K_SMOOTHINGS) * len(D_PERIODS)
    )
    print(
        f"{len(SYMBOLS)}通貨 × {len(SIDES)}方向 × "
        f"期間{len(STOCHASTIC_PERIODS)}種類 × "
        f"基準値{len(STOCHASTIC_LEVELS)}種類 × "
        f"K平滑化{len(SLOW_K_SMOOTHINGS)}種類 × "
        f"D期間{len(D_PERIODS)}種類 = {total_cases}ケース"
    )
    print(f"検証期間: {BACKTEST_START_DATE} ~ {BACKTEST_END_DATE}")
    print(f"固定出口: TP{TAKE_PROFIT_PIPS} / SL{STOP_LOSS_PIPS} pips")

    # 通貨別result.csvに加え、全結果をまとめたall_results.csvも作ります。
    all_rows = []
    cases_per_symbol = (
        len(SIDES) * len(STOCHASTIC_PERIODS) * len(STOCHASTIC_LEVELS)
        * len(SLOW_K_SMOOTHINGS) * len(D_PERIODS)
    )
    for symbol in SYMBOLS:
        # この通貨の15分足ASK CSVを読み込みます。
        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_start_index = warmup_count
            warmup_description = f"検証開始前{warmup_count}本"
        elif len(candles) > WARMUP_BARS:
            # 検証前データがなければ、CSV先頭37本を全設定共通で除外します。
            # 短い期間だけ先に取引しないため、設定間の開始位置が揃います。
            entry_start_index = WARMUP_BARS
            warmup_description = f"CSV先頭{WARMUP_BARS}本"
            print(f"[WARN] {symbol}: 検証前データ不足のため{warmup_description}を使用")
        else:
            raise SystemExit(f"{symbol}のデータが不足しています: {len(candles)}本")
        print(
            f"[RUN] {symbol}: {cases_per_symbol}ケース / 検証{backtest_count}本 / "
            f"ウォームアップ: {warmup_description}"
        )
        all_rows.extend(run_symbol(
            candles, symbol, entry_start_index, DEFAULT_OUTPUT_DIR,
        ))

    write_csv(DEFAULT_OUTPUT_DIR / "all_results.csv", all_rows)
    print(f"完了: {DEFAULT_OUTPUT_DIR}")
    print(f"出力行数: {len(all_rows)} / 実行時間: {time.perf_counter() - started:.2f}秒")
    return 0


# このファイルを直接実行したときだけmain()を動かします。
if __name__ == "__main__":
    raise SystemExit(main())

同じようにストキャスティクスの期間・平滑化期間・%D期間・基準値を検証してみたい方は、参考にしてみてください。

FXのストキャスティクスとは

ストキャスティクスは、一定期間の高値・安値に対して、現在の終値がどの位置にあるかを0〜100で表すテクニカル指標です。

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

  • 買われすぎ・売られすぎを確認できる
  • %Kと%Dの2本のラインを使う
  • %Kと%Dのクロスを売買判断に利用できる

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

ストキャスティクスで分かること

ストキャスティクスでは、現在価格が直近の値動きの中で高い位置にあるのか、低い位置にあるのかを確認できます。

一般的には、

  • 80以上:買われすぎ
  • 20以下:売られすぎ

と判断されます。

たとえば数値が90なら、現在価格は一定期間の高値に近い位置にあることを意味するのです。

ただし、80以上だから必ず下落するわけではありません。強いトレンドでは買われすぎ・売られすぎの状態が続くこともあります。

%K・%Dとは

ストキャスティクスでは、主に**%Kと%D**の2本のラインを使います。

%Kは価格変化に比較的素早く反応し、%Dは%Kを平滑化した、より滑らかなラインです。

一般的には、

  • %Kが%Dを下から上へ抜ける:ゴールデンクロス
  • %Kが%Dを上から下へ抜ける:デッドクロス

として確認します。

今回のバックテストでも、売られすぎ圏でのゴールデンクロスをLONG、買われすぎ圏でのデッドクロスをSHORTの条件として使用します。

ストキャスティクスの計算方法

基本となるRaw %Kは、次の式で計算します。

Raw %K =(現在の終値 − 過去N期間の最安値)÷(過去N期間の最高値 − 最安値)×100

たとえば、

  • 最高値:150円
  • 最安値:140円
  • 現在の終値:148円

なら、

(148 − 140)÷(150 − 140)×100 = 80

    となります。

    今回の検証では、このRaw %Kをさらに平滑化してSlow %Kを作り、そこから%Dを計算します。

    Raw %K → Slow %K → %D

    実際のPythonスクリプトでも、%K期間・%K平滑化期間・%D期間をそれぞれ変更できる形で計算しています。

    FXのストキャスティクスでよく使われる設定

    ストキャスティクスでは、%K期間・%K平滑化期間・%D期間・買われすぎ/売られすぎの基準値を設定します。

    代表的なのは、次の設定です。

    • %K期間:14
    • %K平滑化期間:3
    • %D期間:3
    • 買われすぎ:80
    • 売られすぎ:20

    この「14・3・3」と「80・20」の組み合わせは、スローストキャスティクスでよく使われる設定の1つです。

    ただし、期間を短くすると値動きへの反応が速くなり、長くするとラインの動きが滑らかになります。

    また、基準値も80・20だけでなく、70・30や90・10などさまざまな設定があります。

    そのため、一般的な設定がすべての通貨ペアで最適とは限りません。

    そこで今回は、複数の期間・平滑化期間・%D期間・基準値を組み合わせてバックテストを行い、成績を比較します。

    今回のストキャスティクス検証条件

    今回は、ストキャスティクスの設定によって成績がどのように変わるのかを確認するため、5通貨・LONG/SHORTで合計10,500ケースをバックテストします。

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

    • 5通貨・15分足を使用
    • %K期間7種類・%K平滑化期間5種類・%D期間5種類を比較
    • 買われすぎ・売られすぎの基準値6種類を比較
    • %Kと%Dのクロスでエントリー
    • TP50pips・SL30pipsに固定

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

    検証する5通貨と期間

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

    • USDJPY
    • EURJPY
    • GBPJPY
    • EURUSD
    • AUDUSD

    エントリー判定には15分足を使用します。

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

    5通貨それぞれでLONG・SHORTを分けて検証し、通貨ペアや売買方向によって成績に違いがあるのかを比較します。

    検証する%K期間7種類・%K平滑化期間5種類・%D期間5種類

    ストキャスティクスでは、%K期間だけでなく、%Kの平滑化期間や%D期間によってもラインの動きが変わります。

    今回は次の設定を検証します。

    %K期間

    • 5
    • 7
    • 14
    • 18
    • 21
    • 25
    • 28

    %K平滑化期間

    • 1
    • 2
    • 3
    • 4
    • 5

    %D期間

    • 2
    • 3
    • 4
    • 5
    • 6

    これらを組み合わせて、反応の速い設定から滑らかな設定まで幅広く比較します。

    検証する買われすぎ・売られすぎの基準値6種類

    買われすぎ・売られすぎの基準値は、次の6パターンを検証します。

    買われすぎ売られすぎ
    5545
    6040
    7030
    7525
    8020
    9010

    一般的によく使われる80・20だけでなく、より広い基準値や厳しい基準値も含めて比較します。

    LONG・SHORTのエントリー条件

    今回のバックテストでは、%Kと%Dのクロスをエントリー条件にします。

    LONG

    • %Kが%Dを下から上へ抜ける
    • クロス時の%Dが売られすぎ基準値以下

    SHORT

    • %Kが%Dを上から下へ抜ける
    • クロス時の%Dが買われすぎ基準値以上

    条件を満たしたローソク足が確定したあと、次の15分足の始値でエントリーします。

    TP50・SL30に固定して検証

    今回はストキャスティクスの設定差を比較するため、決済条件はすべて同じにします。

    • TP:50pips
    • SL:30pips

    同じ15分足の中でTPとSLの両方に到達した場合は、保守的にSLを優先します。

    以上の条件を組み合わせ、5通貨・LONG/SHORTで合計10,500ケースを比較します。

    5通貨でストキャスティクス10,500ケースを検証

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

    今回は、

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

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

    そのうえでPFの高い順に並べ、%K期間・%K平滑化期間・%D期間・基準値・合計pips・最大ドローダウンもあわせて確認します。

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

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

    USDJPYで成績が良かったストキャスティクス設定

    USDJPY LONG:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    174690/10115+3901.1940380
    275290/10310+1,0201.1878630
    375390/10237+6501.1548870
    455680/20531+1,3501.1429620
    575590/10128+3201.1404530

    USDJPY LONGの1位は、%K期間7・%K平滑化4・%D期間6・基準値90/10です。

    115回の取引で+390pips、PF1.1940、最大DD380pipsとなりました。

    上位5設定のうち4つが90/10となっており、%K期間も7が多く入っています。

    一方、4位の%K期間5・平滑化5・%D6・80/20はPFでは1位に届かなかったものの、+1,350pipsと上位5設定で最も大きな利益となりました。

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

    USDJPY SHORTでは、

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

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

    今回の条件では、USDJPYはSHORTよりLONGの方が良好な設定が多い結果となっています。

    EURJPYで成績が良かったストキャスティクス設定

    EURJPY LONG:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    152590/10174+1,9001.7451290
    274690/10120+1,2801.7232150
    353590/10140+1,2401.5741320
    455390/10136+1,2001.5714270
    554490/10144+1,2001.5333320

    EURJPY LONGの1位は、%K期間5・%K平滑化2・%D期間5・基準値90/10です。

    174回の取引で+1,900pips、PF1.7451、最大DD290pipsとなりました。

    上位5設定すべてで90/10が共通しており、さらに4設定で%K期間5が採用されています。

    今回のEURJPY LONGでは、買われすぎ・売られすぎの判定を厳しくした設定がPF上位に集中しました。

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

    EURJPY SHORTでは、今回設定した3条件をすべて満たす設定はありませんでした。

    合計pipsがプラスになる設定自体は確認できましたが、PF1.05以上まで条件を引き上げると候補は残りませんでした

    そのため、今回のEURJPYではLONGとSHORTで成績に大きな差が出ています。

    GBPJPYで成績が良かったストキャスティクス設定

    GBPJPY LONG:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    1143590/10405+2,4101.3602320
    2282680/201,062+5,9001.3333440
    3252680/201,076+5,5601.3074420
    4143580/201,176+6,0001.3030670
    5143575/251,380+6,9201.2973680

    GBPJPY LONGの1位は、%K期間14・%K平滑化3・%D期間5・基準値90/10です。

    405回の取引で+2,410pips、PF1.3602、最大DD320pipsとなりました。

    一方、5位の75/25ではPFは1.2973ながら、+6,920pipsと上位5設定で最大の利益となっています。

    GBPJPY LONGでは、基準値を厳しくするとPFが上がりやすく、基準値を広げると取引回数や合計pipsが増える傾向が見られました。

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

    GBPJPY SHORTでも、

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

    をすべて満たす設定はありませんでした。

    今回の検証では、GBPJPYもLONG側の成績が明確に優位となっています。

    EURUSDで成績が良かったストキャスティクス設定

    EURUSD LONG:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    1213590/10388+1,2401.1821380
    2213490/10422+1,2601.1694360
    355680/20437+1,2101.1563490
    4251590/10423+1,1501.1533320
    573690/10216+5601.1458240

    EURUSD LONGの1位は、%K期間21・%K平滑化3・%D期間5・基準値90/10です。

    388回の取引で+1,240pips、PF1.1821となりました。

    上位5設定のうち4つが90/10となっており、EURUSD LONGでも厳しい基準値がPF上位に入りやすい結果となっています。

    EURUSD SHORT:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    154590/10127+5101.2329300
    255490/10128+4801.2162300
    372690/10232+8001.1975360
    452490/10314+9801.1775530
    553590/10172+5201.1716360

    EURUSD SHORTでは、上位5設定すべてが90/10となりました。

    1位は%K期間5・平滑化4・%D5で、PF1.2329です。

    LONG・SHORTの両方向で基準を満たす設定が複数見つかっており、クロス円とは異なる結果となっています。

    AUDUSDで成績が良かったストキャスティクス設定

    AUDUSD LONG:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    151490/10297+805.11.1534500
    2181375/25493+1,266.21.1445710
    3181590/10326+822.31.1420560
    4185690/10231+549.41.1337270
    553390/10293+692.31.1326750

    AUDUSD LONGの1位は、%K期間5・%K平滑化1・%D期間4・基準値90/10です。

    297回の取引で+805.1pips、PF1.1534となりました。

    一方、2位の%K期間18・平滑化1・%D3・75/25はPFではわずかに下回ったものの、+1,266.2pipsと上位5設定で最も大きな利益となっています。

    AUDUSD SHORT:TOP5

    順位%K期間%K平滑化%D期間基準値取引回数合計pipsPF最大DD
    151590/10234+6601.1594360
    2213390/10385+9301.1354780
    352590/10203+4701.1295310
    4282490/10379+8701.1283910
    5283390/10388+8401.1207720

    AUDUSD SHORTでも、上位5設定すべてが90/10となりました。

    1位は%K期間5・平滑化1・%D5でPF1.1594です。

    一方、2位はPF1.1354ながら+930pipsとなっており、合計利益では1位を上回っています。

    AUDUSDはEURUSDと同様に、LONG・SHORTの両方向で基準を満たす設定が複数確認できました

    5通貨のストキャスティクス検証結果を比較

    5通貨のLONG・SHORTで、合計損益が最も大きかった設定を比較すると次のようになりました。

    通貨方向%K期間%K平滑化%D期間基準値合計pipsPF
    USDJPYLONG72455/45+2,8701.087
    USDJPYSHORT54290/10-100.999
    EURJPYLONG72370/30+4,9801.167
    EURJPYSHORT255675/25+5001.024
    GBPJPYLONG144260/40+8,0401.237
    GBPJPYSHORT71290/10+2301.009
    EURUSDLONG251680/20+1,4801.143
    EURUSDSHORT284360/40+1,1901.098
    AUDUSDLONG181375/25+1,266.21.145
    AUDUSDSHORT213390/10+9301.135

    今回の検証で特に成績が良かったのは、GBPJPYのLONGです。

    合計+8,040pips、PF1.237となり、EURJPYやUSDJPYのLONGと比べても大きな差が出ました。

    また、USDJPY・EURJPY・GBPJPYではLONGの成績がSHORTを大きく上回っています。

    一方、EURUSDとAUDUSDではLONGだけでなくSHORTでもプラスとなり、特にAUDUSD SHORTはPF1.135となりました。

    設定値については、すべての通貨に共通する1つの最適設定は確認できませんでした。

    たとえば%K期間だけでも7・14・18・21・25・28と幅があり、%K平滑化期間や%D期間にもばらつきがあります。

    そのため、今回の結果からはストキャスティクスに万能な設定があるというより、通貨ペアやLONG・SHORTによって適した設定が異なると考えられます。

    また、一般的によく使われる80/20だけでなく、55/45・60/40・70/30・75/25・90/10も上位に入りました。

    今回の10,500ケースでは、設定値を固定するよりも、通貨ペアや売買方向に合わせて期間・平滑化・基準値を調整することが重要という結果になりました。

    ストキャスティクスをFXで使う際の注意点

    ストキャスティクスは使いやすい指標ですが、設定やバックテスト結果の見方には注意が必要です。

    主な注意点は次の5つです。

    • ストキャスティクスだけでエントリーを判断しない
    • 買われすぎ・売られすぎだけで逆張りしない
    • 取引回数が少ない設定は慎重に判断する
    • 1位の設定だけで判断しない
    • バックテストではスプレッドや約定差に注意する

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

    ストキャスティクスだけでエントリーを判断しない

    ストキャスティクスは反転のタイミングを確認しやすい一方で、単体ではダマシも発生します。

    今回の検証でも、設定によって成績には大きな差が出ました。

    実際に使う場合は、移動平均線やADXなどを組み合わせて、相場の方向やトレンドの強さも確認する方法があります。

    買われすぎ・売られすぎだけで逆張りしない

    80以上だからSHORT、20以下だからLONGと単純に判断するのは注意が必要です。

    今回のバックテストでは、買われすぎ・売られすぎの基準値だけでなく、%Kと%Dのクロスも確認してからエントリーしています。

    基準値はあくまでエントリー候補を絞る条件として使うのがよいでしょう。

    取引回数が少ない設定は慎重に判断する

    合計pipsやPFが高くても、取引回数が極端に少ない場合は注意が必要です。

    数十回程度の取引だけで高い成績が出ていても、少数の勝ちトレードによって結果が大きく変わっている可能性があります。

    そのため、成績を比較する際は、合計pipsやPFだけでなく取引回数も確認することが重要です。

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

    10,500ケースを比較すると、設定によって細かな成績差が出ます。

    しかし、1位の設定だけが突出して良く、近い設定では大きく成績が落ちている場合は、過去の相場に偶然合っていた可能性もあります。

    そのため、上位設定だけでなく、近い期間や基準値でも安定して良い結果が出ているかを確認することが大切です。

    バックテストではスプレッドや約定差に注意する

    今回の検証は、ストキャスティクスの設定を同じ条件で比較することを目的としています。

    そのため、実際の取引で発生するスプレッドやスリッページなどを完全に再現したものではありません。

    実運用ではバックテストより成績が悪化する可能性があるため、バックテストの利益をそのまま実際の利益として考えないようにしましょう。

    FXのストキャスティクスに関するよくある質問

    ファストストキャスティクスとスローストキャスティクスの違いは?

    主な違いは、%Kを平滑化するかどうかです。

    ファストストキャスティクスは値動きへの反応が速い一方、細かな変動にも反応しやすくなります。

    スローストキャスティクスは%Kを平滑化するため、ラインの動きが比較的滑らかになります。

    今回の検証では、Raw %Kを平滑化してSlow %Kを計算する方法を使用していました。

    ストキャスティクスとRSIの違いは?

    どちらも買われすぎ・売られすぎの判断に使われますが、計算方法が異なります。

    • ストキャスティクス:一定期間の高値・安値に対する現在価格の位置を見る
    • RSI:一定期間の上昇幅と下落幅から値動きの強さを見る

    似た用途で使われますが、確認している値は異なります。

    ストキャスティクスはトレンド相場でも使える?

    トレンド相場でも使えますが、買われすぎ・売られすぎだけで逆張りする場合は注意が必要です。

    強い上昇相場では高値圏、強い下降相場では安値圏にストキャスティクスが長くとどまることがあります。

    そのため、トレンド相場では押し目や戻りのタイミングを確認する指標として使う方法もあります。

    ストキャスティクスはどの時間足で使える?

    15分足・1時間足・4時間足・日足など、さまざまな時間足で利用できます。

    ただし、時間足によって値動きやシグナルの回数が変わるため、同じ設定がすべての時間足で同じように機能するとは限りません。

    今回のバックテストでは、条件を統一して15分足を使用しています。

    ストキャスティクスは他のテクニカル指標と組み合わせた方がいい?

    ストキャスティクス単体でも売買条件を作れますが、他のテクニカル指標と組み合わせる方法もあります。

    たとえば、

    • SMA・EMA:相場の方向を確認
    • ADX:トレンドの強さを確認
    • ATR:値動きの大きさを確認

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

    ストキャスティクスでエントリータイミングを確認し、別の指標で相場環境を絞り込むことで、単体とは異なる売買ルールを作ることができます。

    まとめ

    今回は、USDJPY・EURJPY・GBPJPY・EURUSD・AUDUSDの5通貨を対象に、%K期間7種類・%K平滑化期間5種類・%D期間5種類・基準値6種類・LONG/SHORTを組み合わせた10,500ケースを検証しました。

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

    • 一般的な14・3・3、80/20が最適とは限らない
    • 通貨ペアやLONG・SHORTによって良い設定が異なる
    • GBPJPY LONGが特に好成績だった
    • USDJPY SHORTでは有効な設定を確認できなかった
    • 期間・平滑化・基準値をまとめて検証することが重要

    今回、合計損益が最も大きかったのは、GBPJPY LONGの%K期間14・%K平滑化期間4・%D期間2・基準値60/40で+8,040pips、PF1.237となった設定でした。

    一方、USDJPY SHORTでは最も成績が良かった設定でも合計損益が-10pips、PF0.999となり、今回の検証では有効な設定を確認できませんでした。

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

    検証期間も2024年1月1日〜2026年7月31日に限定しているため、今回成績が良かった設定が今後も同じように機能するとは限りません。

    実際にストキャスティクスをトレードへ取り入れる場合は、一般的な設定をそのまま使うのではなく、通貨ペアや売買方向に合わせてバックテストしたうえで判断してください。

    -FX自動売買, Python