PythonでRSI+MACDのバックテストを実装!組み合わせ戦略の勝率を徹底検証

Python実装・コード

RSI+MACDの組み合わせ戦略とは

テクニカル分析において、RSI(Relative Strength Index)とMACD(Moving Average Convergence Divergence)は個人投資家から機関投資家まで広く使われる指標です。それぞれ単体で使うより、組み合わせることでシグナルの精度を高められます。本記事では、Pythonでこの組み合わせ戦略をゼロから実装し、バックテストで実際の勝率を検証する方法を解説します。

RSIとMACDの基本をおさらい

RSI(相対力指数)

RSIは0〜100の値をとり、30以下が売られすぎ(買いシグナル)70以上が買われすぎ(売りシグナル)の目安です。

MACD(移動平均収束拡散法)

MACDは短期EMA(12日)と長期EMA(26日)の差で、シグナル線(9日EMA)とのクロスで売買タイミングを判断します。MACDがシグナルを上抜けで買い、下抜けで売りが基本です。

Pythonで実装:環境準備

# 必要ライブラリのインストール
# pip install yfinance pandas numpy matplotlib ta

import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import ta  # Technical Analysis library

株価データの取得とテクニカル指標の計算

def fetch_and_calculate_indicators(ticker: str, period: str = "2y") -> pd.DataFrame:
    """
    株価データを取得してRSIとMACDを計算する
    Args:
        ticker: ティッカーシンボル(例: '7203.T')
        period: データ期間(例: '1y', '2y', '5y')
    """
    df = yf.download(ticker, period=period, auto_adjust=True)
    df = df.dropna()
    
    # RSI計算(14日)
    df['RSI'] = ta.momentum.RSIIndicator(df['Close'], window=14).rsi()
    
    # MACD計算(12, 26, 9)
    macd_indicator = ta.trend.MACD(df['Close'], window_fast=12, window_slow=26, window_sign=9)
    df['MACD'] = macd_indicator.macd()
    df['MACD_signal'] = macd_indicator.macd_signal()
    df['MACD_hist'] = macd_indicator.macd_diff()
    
    # 移動平均線(トレンド判断用)
    df['MA50'] = df['Close'].rolling(50).mean()
    df['MA200'] = df['Close'].rolling(200).mean()
    
    return df.dropna()

# 実行例:トヨタ自動車の2年分データ
df = fetch_and_calculate_indicators('7203.T', '2y')
print(df[['Close', 'RSI', 'MACD', 'MACD_signal']].tail(10))

売買シグナルの定義

def generate_signals(df: pd.DataFrame) -> pd.DataFrame:
    """
    RSI + MACD の組み合わせシグナルを生成
    買い条件: RSI < 45 かつ MACDがシグナルを上抜け
    売り条件: RSI > 55 かつ MACDがシグナルを下抜け
    """
    df = df.copy()
    
    # MACDクロスの判定
    df['MACD_cross_up'] = (
        (df['MACD'] > df['MACD_signal']) & 
        (df['MACD'].shift(1) <= df['MACD_signal'].shift(1))
    )
    df['MACD_cross_down'] = (
        (df['MACD'] < df['MACD_signal']) & 
        (df['MACD'].shift(1) >= df['MACD_signal'].shift(1))
    )
    
    df['signal'] = 0
    # 買いシグナル
    df.loc[(df['MACD_cross_up'] == True) & (df['RSI'] < 45), 'signal'] = 1
    # 売りシグナル
    df.loc[(df['MACD_cross_down'] == True) & (df['RSI'] > 55), 'signal'] = -1
    
    return df

df = generate_signals(df)
buy_signals = df[df['signal'] == 1]
sell_signals = df[df['signal'] == -1]
print(f"買いシグナル数: {len(buy_signals)}")
print(f"売りシグナル数: {len(sell_signals)}")

シンプルなバックテストの実装

def simple_backtest(df: pd.DataFrame, initial_capital: float = 1_000_000) -> dict:
    """シンプルなバックテスト(手数料0.1%考慮)"""
    capital = initial_capital
    position = 0
    entry_price = 0.0
    commission_rate = 0.001
    trades = []
    equity_curve = [capital]
    
    for i in range(len(df)):
        row = df.iloc[i]
        price = float(row['Close'])
        signal = row['signal']
        
        # 買いシグナル & ポジションなし
        if signal == 1 and position == 0:
            shares = int(capital * 0.95 / price / 100) * 100  # 100株単位
            if shares > 0:
                cost = shares * price * (1 + commission_rate)
                capital -= cost
                position = shares
                entry_price = price
        
        # 売りシグナル & ポジションあり
        elif signal == -1 and position > 0:
            proceeds = position * price * (1 - commission_rate)
            pnl = proceeds - (position * entry_price)
            pnl_pct = (price - entry_price) / entry_price * 100
            trades.append({'entry': entry_price, 'exit': price, 'pnl': pnl, 'pnl_pct': pnl_pct})
            capital += proceeds
            position = 0
        
        equity_curve.append(capital + position * price)
    
    if not trades:
        return {'error': 'トレードが発生しませんでした'}
    
    trades_df = pd.DataFrame(trades)
    total_return = (equity_curve[-1] - initial_capital) / initial_capital * 100
    win_rate = len(trades_df[trades_df['pnl'] > 0]) / len(trades_df) * 100
    
    # 最大ドローダウン
    equity_series = pd.Series(equity_curve)
    drawdown = (equity_series - equity_series.cummax()) / equity_series.cummax() * 100
    
    return {
        'total_return': round(total_return, 2),
        'win_rate': round(win_rate, 2),
        'total_trades': len(trades_df),
        'max_drawdown': round(drawdown.min(), 2),
        'final_capital': round(equity_curve[-1], 0)
    }

results = simple_backtest(df)
print("=== バックテスト結果 ===")
for key, value in results.items():
    print(f"{key}: {value}")

バックテスト結果の解釈と改善ポイント

バックテストで確認すべき主要指標:

  • 勝率:50%以上が目安。ただし平均利益と平均損失の比率(リスクリワード比)も重要
  • 最大ドローダウン:-20%以内が許容範囲の目安
  • 総トレード数:統計的有意性のため最低30〜50回以上必要

改善のヒント:RSIの閾値をパラメータ最適化で調整する、MA200超のみ買いに絞るトレンドフィルタを追加するなどが有効です。ただし最適化しすぎるとオーバーフィッティングになるため注意が必要です。

まとめ・次のステップ

RSI+MACDの組み合わせ戦略をPythonでゼロから実装する方法を解説しました。今回のコードをベースに、ボリンジャーバンドを追加したり、複数銘柄で同時テストしたりと発展させてみてください。次のステップとして、backtradervectorbtなどの専用バックテストフレームワークを使うと、スリッページや資金管理など、より実践的な検証が可能になります。

タイトルとURLをコピーしました