『黄金のスキャルピングFX』EMA13手法を13年分バックテスト検証→ランダムより勝率低い
話題の本を検証してみた
「黄金のスキャルピングFX」という本がある。
EMA13とEMA100を使った押し目買い・戻り売り。シンプルで美しいロジック。
本を読んで「これは検証してみる価値がある」と思った。
結果はどうだったのか?13年分のデータで検証した。
※本記事は書籍に記載された手法を客観的にバックテスト検証したものです。投資判断は自己責任でお願いします。
検証した手法の条件
本に書いてある通りに実装した。
エントリー条件
買いエントリー:
- EMA100が右肩上がり(上昇トレンド)
- 価格がEMA13にタッチ(上から下に接近)
- でも終値はEMA13の上で確定
売りエントリー:
- EMA100が右肩下がり(下降トレンド)
- 価格がEMA13にタッチ(下から上に接近)
- でも終値はEMA13の下で確定
決済条件
| 項目 | 設定値 |
|---|---|
| TP(利確) | 10pips |
| SL(損切) | 10pips |
| RR比 | 1:1 |
フィルター
本に書いてあるフィルターも全部入れた:
- 時間フィルター: JST 8:00〜24:00のみ(深夜は避ける)
- ラウンドナンバーフィルター: 50pips単位(150.00、150.50など)の±5pips圏内は避ける
その他条件
| 項目 | 設定値 |
|---|---|
| 通貨ペア | USDJPY |
| 時間足 | 1分足 |
| スプレッド | 0.2pips(往復で0.2pips) |
| 検証期間 | 2012年1月〜2025年5月 |
13年分、約500万本のローソク足で検証。
結果:-10,180pips
| 指標 | 結果 |
|---|---|
| 総トレード数 | 36,358回 |
| 勝ち | 17,670回 |
| 負け | 18,688回 |
| 勝率 | 48.6% |
| 合計pips | -10,180pips |
| 平均pips | -0.28pips |
13年間で1万pips以上の損失。
0.1ロット(1万通貨)で回してたら約-1,000万円。
勝敗分布:RR 1:1の残酷な現実
RR 1:1(TP10pips / SL10pips)での勝敗:
| 結果 | 回数 | 割合 |
|---|---|---|
| 勝ち(+10pips) | 17,670回 | 48.6% |
| 負け(-10pips) | 18,688回 | 51.4% |
勝ち vs 負け = 17,670 vs 18,688
負けが1,018回多い。
損益 = (17,670 × 10) - (18,688 × 10) = 176,700 - 186,880 = -10,180pipsRR 1:1では、勝率50%でトントン。48.6%では確実に負ける。
買い vs 売り
| 方向 | トレード数 | 勝率 | 合計pips |
|---|---|---|---|
| 買い | 18,086回 | 49.0% | -3,640pips |
| 売り | 18,272回 | 48.2% | -6,540pips |
両方マイナス。売りのほうがひどい。
衝撃の事実:ランダムエントリーのほうが勝率が高い
EMA13手法に優位性があるのか?
ランダムにエントリーした場合と比較してみた。
ランダムエントリー検証(5万回サンプル)
すべての終値からランダムにエントリーして、TP10pips/SL10pipsでどちらに先に到達するか検証。
| エントリー方法 | 勝率 |
|---|---|
| ランダム(買い) | 51.05% |
| ランダム(売り) | 49.05% |
| ランダム(合計) | 50.05% |
| EMA13手法 | 48.6% |
驚きの結論
ランダムにエントリーしたほうが勝率が高い!
- ランダム: 50.05%
- EMA13手法: 48.6%
- 差: -1.45%
EMA13手法は、エントリータイミングを「悪化」させている。
何もしないでランダムに入ったほうがマシ。
なぜ負けるのか?数学的に解説
RR 1:1の損益分岐点
TP10pips、SL10pipsのRR 1:1の場合:
損益分岐勝率 = 50%(スプレッドがエントリー価格に織り込み済みの場合)実際の勝率は48.6%。1.4%足りない。
ランダムより悪い理由
買いの場合、ランダムエントリーでは**51.05%**勝てる(USDJPYは長期的に上昇傾向)。
でもEMA13手法の買いは49.0%。
「EMA13タッチ」という条件が、むしろ不利なタイミングを選んでしまっている。
期待値計算
期待値 = (勝率 × TP) - (敗率 × SL) = (0.486 × 10) - (0.514 × 10) = 4.86 - 5.14 = -0.28pips/トレード1回トレードするごとに0.28pips失う。
36,358回 × -0.28pips = -10,180pips
計算通り。
時間帯別勝率
| 時間(JST) | 勝率 |
|---|---|
| 8時 | 48% |
| 9時 | 47% |
| 10時 | 48% |
| 11時 | 49% |
| ... | ... |
| 22時 | 51% |
| 23時 | 49% |
22時だけギリギリ51%。他は全部50%未満。
「時間帯を絞れば勝てる」という希望もない。
累積損益グラフ
開始直後から一直線に下落。
- 5,000トレード時点:-1,500pips
- 10,000トレード時点:-3,000pips
- 20,000トレード時点:-6,000pips
- 36,000トレード時点:-10,000pips以上
一度もプラス圏に浮上することなく沈み続けた。
なぜこの手法は機能しないのか
問題1:EMA13タッチは頻繁すぎる
1分足でEMA13へのタッチはめちゃくちゃ頻繁に起きる。
ノイズだらけの1分足で「タッチしたから押し目」は無理がある。
問題2:EMA100の傾きだけではトレンド判定が弱い
EMA100が「右肩上がり」かどうかを5本前との差で判定。
レンジ相場でも微妙に上向きならエントリーしてしまう。
問題3:RR 1:1では優位性が必要
TP=SL=10pipsの1:1では、50%以上の勝率がないと負ける。
この手法にはその優位性がない。
問題4:1分足のノイズ
1分足はノイズの塊。
テクニカル分析が機能しにくい時間足で、シンプルなEMAクロス系は厳しい。
検証に使用したPythonコード
完全公開。誰でも再現検証できる。
# -*- coding: utf-8 -*-"""黄金のスキャルピングFX - EMA13パターン検証1分足 USDJPY / EMA13 + EMA100 / TP10pips SL10pipsフィルター: JST 8:00-24:00 / ラウンドナンバー±5pips避ける"""
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport os
def load_m1_data(symbol='USDJPY'): """1分足データを読み込む""" path = f'C:/Users/yourpath/fx_data/{symbol}_M1.csv'
if not os.path.exists(path): print(f"データファイルが見つかりません: {path}") return None
# MT4/MT5形式: 日付 時刻,Open,High,Low,Close,Volume df = pd.read_csv(path, header=None, names=['datetime', 'open', 'high', 'low', 'close', 'volume']) df['datetime'] = pd.to_datetime(df['datetime'], format='%Y.%m.%d %H:%M') df = df.set_index('datetime')
return df[['open', 'high', 'low', 'close']].copy()
def calculate_ema(df, period): """EMAを計算""" return df['close'].ewm(span=period, adjust=False).mean()
def is_round_number_zone(price, zone_pips=5): """ラウンドナンバー±zone_pips内かどうか判定""" # USDJPYの場合、50pips単位がラウンドナンバー(150.00、150.50など) round_price = round(price * 2) / 2 distance_pips = abs(price - round_price) * 100 return distance_pips <= zone_pips
def backtest_golden_scalping(df, tp_pips=10, sl_pips=10, spread_pips=0.2): """黄金のスキャルピングEMA13パターンのバックテスト"""
# EMA計算 df['ema13'] = calculate_ema(df, 13) df['ema100'] = calculate_ema(df, 100) df['ema100_slope'] = df['ema100'].diff(5) # 5本前との差でトレンド判定
# JST時間を計算(UTCから+9時間) df['hour_jst'] = (df.index.hour + 9) % 24
trades = [] position = None
for i in range(101, len(df) - 100): current = df.iloc[i] prev = df.iloc[i-1]
# 時間フィルター: JST 8:00-24:00のみ if current['hour_jst'] < 8: continue
# ラウンドナンバーフィルター if is_round_number_zone(current['close'], 5): continue
# ポジションなしの場合、エントリー判定 if position is None: # 買いエントリー条件 if (current['ema100_slope'] > 0 and # EMA100上向き prev['close'] > prev['ema13'] and # 前回は上 current['low'] <= current['ema13'] and # 今回タッチ current['close'] > current['ema13']): # でも終値は上
entry_price = current['close'] + spread_pips / 100 position = { 'type': 'BUY', 'entry_time': df.index[i], 'entry_price': entry_price, 'tp': entry_price + tp_pips / 100, 'sl': entry_price - sl_pips / 100 }
# 売りエントリー条件 elif (current['ema100_slope'] < 0 and # EMA100下向き prev['close'] < prev['ema13'] and # 前回は下 current['high'] >= current['ema13'] and # 今回タッチ current['close'] < current['ema13']): # でも終値は下
entry_price = current['close'] - spread_pips / 100 position = { 'type': 'SELL', 'entry_time': df.index[i], 'entry_price': entry_price, 'tp': entry_price - tp_pips / 100, 'sl': entry_price + sl_pips / 100 }
# ポジションありの場合、決済判定 else: if position['type'] == 'BUY': if current['high'] >= position['tp']: position['exit_time'] = df.index[i] position['pips'] = tp_pips position['result'] = 'WIN' trades.append(position) position = None elif current['low'] <= position['sl']: position['exit_time'] = df.index[i] position['pips'] = -sl_pips position['result'] = 'LOSE' trades.append(position) position = None
else: # SELL if current['low'] <= position['tp']: position['exit_time'] = df.index[i] position['pips'] = tp_pips position['result'] = 'WIN' trades.append(position) position = None elif current['high'] >= position['sl']: position['exit_time'] = df.index[i] position['pips'] = -sl_pips position['result'] = 'LOSE' trades.append(position) position = None
return pd.DataFrame(trades)
def main(): # データ読み込み df = load_m1_data('USDJPY') if df is None: return
print(f"データ期間: {df.index[0]} 〜 {df.index[-1]}") print(f"データ件数: {len(df):,}件")
# バックテスト実行 trades_df = backtest_golden_scalping(df, tp_pips=10, sl_pips=10, spread_pips=0.2)
# 結果表示 total = len(trades_df) wins = len(trades_df[trades_df['result'] == 'WIN']) losses = len(trades_df[trades_df['result'] == 'LOSE'])
print(f"総トレード数: {total}") print(f"勝ち: {wins} / 負け: {losses}") print(f"勝率: {wins/total*100:.1f}%") print(f"合計pips: {trades_df['pips'].sum():.1f}")
if __name__ == '__main__': main()データの準備
MT4/MT5から1分足データをCSV形式でエクスポートして使用。
2012.01.03 07:00,76.886,76.886,76.885,76.886,32012.01.03 07:01,76.885,76.886,76.885,76.886,2...検証から学べること
重要な注意点
- 本の手法が「間違っている」わけではない
- 特定の相場環境では機能した可能性がある
- 著者の意図した使い方と異なる可能性もある
- バックテストと裁量トレードは別物
学んだこと
- 本や商材の手法は必ずバックテストしろ
- 「シンプルで美しいロジック」=「勝てる」ではない
- RR 1:1なら勝率50%以上必要
- 1分足スキャルピングは超高難度
まとめ
| 項目 | 結果 |
|---|---|
| 検証期間 | 2012年〜2025年(13年間) |
| 総トレード数 | 36,358回 |
| 勝率 | 48.6% |
| 合計損益 | -10,180pips |
検証結果の解釈
機械的にルール通り実行した場合、この手法は長期的にマイナスになる。
ただし:
- 裁量判断を加えれば結果は変わる可能性がある
- 特定の相場環境に絞れば機能するかもしれない
- 著者の意図した「使いどころ」がある可能性
本記事の目的
この検証は手法を批判するためではなく、客観的なデータを提供するために行った。
投資判断は各自の責任で。
教訓
「本に書いてある=勝てる」ではない。 必ず自分でバックテストしろ。
次に気になる手法があったら、まず検証。
それが本当の黄金ルールだ。
関連記事
🎁おすすめツール・サービス
※上記リンクにはプロモーション(広告)が含まれています
量子相場謀略|ホソノP
ブログでは書けない本音のトレード戦略とEA開発の裏側を公開。 データに基づいた検証で、再現性のある手法だけをお届けします。