Pythonでボリンジャーバンドブレイクアウトを実装してバックテストしてみた【日本株対応】

先週、ドル円のトレードでまた損切りをした。エントリーのタイミングが悪かったのはわかってたんだけど、何を根拠に「ここだ」と判断すればいいのか、いまだにぼんやりしてる。で、ずっと気になってたボリンジャーバンドをちゃんと自分で実装して、数字で確かめてみようと思い立った。

ボリンジャーバンドって何?

ボリンジャーバンドは、移動平均線(MA)を中心に、上下に標準偏差の±2σを描いたバンドのこと。価格がバンドの端に触れたとき「行き過ぎ=反転サイン」と読む使い方が多い。理論上、価格の約95%がこのバンドに収まるらしい。

名前は知ってたけど、ちゃんと理解してなかった。自分で計算してみることにした。

Pythonで実装してみる

今回はトヨタ(7203)の日足データを使った。yfinanceで取得できる。

import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt

# データ取得
df = yf.download("7203.T", start="2024-01-01", end="2026-06-30")
close = df["Close"]

# ボリンジャーバンド計算
window = 20  # 20日移動平均
ma = close.rolling(window).mean()
std = close.rolling(window).std()

upper = ma + 2 * std  # +2σ
lower = ma - 2 * std  # -2σ

# プロット
plt.figure(figsize=(12, 6))
plt.plot(close, label="終値", color="#f0f6fc")
plt.plot(ma, label="MA20", color="#f59e0b", linewidth=1.5)
plt.plot(upper, label="+2σ", color="#34d399", linestyle="--")
plt.plot(lower, label="-2σ", color="#34d399", linestyle="--")
plt.fill_between(close.index, upper, lower, alpha=0.1, color="#34d399")
plt.legend()
plt.title("トヨタ(7203)ボリンジャーバンド")
plt.tight_layout()
plt.savefig("toyota_bb.png", dpi=100, facecolor="#0d1117")
plt.close()

実装自体はシンプルで、rolling().std()で標準偏差が出る。難しくない。

バックテスト:「-2σ割れで買い、+2σ超えで売り」戦略

一番シンプルな逆張り戦略でバックテストしてみた。

def bollinger_backtest(close, window=20):
    ma = close.rolling(window).mean()
    std = close.rolling(window).std()
    upper = ma + 2 * std
    lower = ma - 2 * std

    position = 0
    entry_price = 0
    trades = []

    for i in range(window, len(close)):
        price = close.iloc[i]
        lb = lower.iloc[i]
        ub = upper.iloc[i]

        if position == 0 and price < lb:
            # -2σ割れ → 買いエントリー
            position = 1
            entry_price = price

        elif position == 1 and price > ma.iloc[i]:
            # MA回帰 → 決済
            pnl = (price - entry_price) / entry_price * 100
            trades.append({"entry": entry_price, "exit": price, "pnl_%": round(pnl, 2)})
            position = 0

    return pd.DataFrame(trades)

results = bollinger_backtest(close)
print(f"トレード数: {len(results)}")
print(f"勝率: {(results['pnl_%'] > 0).mean():.1%}")
print(f"平均損益: {results['pnl_%'].mean():.2f}%")
print(f"合計損益: {results['pnl_%'].sum():.2f}%")

結果

トヨタ(7203)の2024年〜2026年6月のデータで回した結果はこんな感じだった:

  • トレード数:約18回
  • 勝率:約61%
  • 平均損益:+1.4%/トレード

悪くない。ただ、トレード数が少ないのと、スリッページや手数料を考えていないのでそのまま信用はできない。

やってみてわかったこと

ボリンジャーバンドをゼロから実装してみて気づいたのは、「バンドの幅自体が市場のボラティリティ」を表してるってこと。値幅が大きいときほどバンドが広がる。当たり前っちゃ当たり前なんだけど、チャートで見るだけじゃ意識してなかった。

あと、日本株は結構ゆっくり動くから、短期の逆張りよりMAへの回帰狙いのほうが合ってるかもしれない。ドル円とはちょっと性質が違う。

次はパラメータ(window期間やσの幅)を変えてもっと細かくチューニングしてみたい。窓期間を10日・20日・50日で比べてみるつもり。

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