日立や三菱電機みたいな製造メーカーを長年見てきて、「決算発表の後にバン!って動く動きがあるよな」とずっと思ってた。でも手動でチャートを眺めながら「これはブレイクアウトか?」を判断するのは、育児中の今はもう無理。だからPythonでボリンジャーバンドのブレイクアウトを自動検出できるようにした。思ったより精度が良くてびっくりしてる。
ボリンジャーバンドとは(簡単おさらい)
ボリンジャーバンドは移動平均線を中心に、上下に「標準偏差×倍率」の帯を引いたもの。一般的な設定は20日移動平均 ± 2σ。株価がバンドの外側に飛び出すと「統計的に異常な動き」として注目される。
使い方は主に2つあって、「バンドタッチで反転を狙う(逆張り)」と「バンドを突き抜けたらトレンド継続(順張り)」。今回は後者のブレイクアウト戦略。製造メーカー株は一度トレンドが出ると続きやすい印象があって、順張りのほうが僕の体感に合ってる。
Pythonで実装する
まずライブラリのインストール。
pip install yfinance pandas numpy matplotlib
次にボリンジャーバンドの計算とブレイクアウト検出。
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.family'] = 'IPAGothic' # 日本語フォント(環境に応じて変更)
def get_bollinger_bands(ticker, period=20, std_dev=2, start="2023-01-01", end="2025-12-31"):
"""ボリンジャーバンドを計算して返す"""
df = yf.download(ticker, start=start, end=end)
df = df[["Close"]].dropna()
df["MA"] = df["Close"].rolling(period).mean()
df["STD"] = df["Close"].rolling(period).std()
df["Upper"] = df["MA"] + std_dev * df["STD"]
df["Lower"] = df["MA"] - std_dev * df["STD"]
df = df.dropna()
return df
def detect_breakout(df):
"""ブレイクアウトシグナルを検出する"""
df = df.copy()
# 上抜けブレイクアウト(前日はバンド内 → 当日はバンド外)
df["prev_close"] = df["Close"].shift(1)
df["prev_upper"] = df["Upper"].shift(1)
df["prev_lower"] = df["Lower"].shift(1)
df["breakout_up"] = (df["Close"] > df["Upper"]) & (df["prev_close"] <= df["prev_upper"])
df["breakout_down"] = (df["Close"] < df["Lower"]) & (df["prev_close"] >= df["prev_lower"])
return df
# 日立(6501.T)で試す
ticker = "6501.T"
df = get_bollinger_bands(ticker)
df = detect_breakout(df)
# ブレイクアウト日を確認
up_breakouts = df[df["breakout_up"]]
down_breakouts = df[df["breakout_down"]]
print(f"上抜けブレイクアウト: {len(up_breakouts)}回")
print(f"下抜けブレイクアウト: {len(down_breakouts)}回")
print("\n直近の上抜けブレイクアウト:")
print(up_breakouts[["Close", "Upper", "Lower"]].tail(5))
ブレイクアウト戦略のバックテスト
シグナルが出た翌日に買い、一定日数後に手仕舞いするシンプルな戦略でバックテスト。
def backtest_breakout(df, hold_days=5):
"""
ブレイクアウト翌日に買い、hold_days後に売るバックテスト
"""
trades = []
for i in range(len(df) - hold_days - 1):
if df["breakout_up"].iloc[i]:
# 翌日の始値で買い(終値で近似)
entry_price = df["Close"].iloc[i + 1]
exit_price = df["Close"].iloc[i + 1 + hold_days]
return_pct = (exit_price - entry_price) / entry_price * 100
trades.append({
"entry_date": df.index[i + 1],
"exit_date": df.index[i + 1 + hold_days],
"entry_price": entry_price,
"exit_price": exit_price,
"return_pct": return_pct
})
trades_df = pd.DataFrame(trades)
if trades_df.empty:
print("トレードなし")
return trades_df
print(f"総トレード数: {len(trades_df)}")
print(f"勝率: {(trades_df['return_pct'] > 0).mean() * 100:.1f}%")
print(f"平均リターン: {trades_df['return_pct'].mean():.2f}%")
print(f"最大利益: {trades_df['return_pct'].max():.2f}%")
print(f"最大損失: {trades_df['return_pct'].min():.2f}%")
return trades_df
trades = backtest_breakout(df, hold_days=5)
チャートで確認する
def plot_bollinger_with_signals(df, ticker_name):
"""ボリンジャーバンドとシグナルをプロット"""
fig, ax = plt.subplots(figsize=(14, 6))
# ボリンジャーバンド
ax.plot(df.index, df["Close"], label="終値", color="black", linewidth=1)
ax.plot(df.index, df["MA"], label="20日MA", color="blue", linewidth=1, linestyle="--")
ax.plot(df.index, df["Upper"], label="上限バンド(+2σ)", color="red", linewidth=1)
ax.plot(df.index, df["Lower"], label="下限バンド(-2σ)", color="green", linewidth=1)
ax.fill_between(df.index, df["Upper"], df["Lower"], alpha=0.05, color="gray")
# ブレイクアウトシグナル
up_signals = df[df["breakout_up"]]
down_signals = df[df["breakout_down"]]
ax.scatter(up_signals.index, up_signals["Close"], marker="^", color="red", s=100, zorder=5, label="上抜けシグナル")
ax.scatter(down_signals.index, down_signals["Close"], marker="v", color="green", s=100, zorder=5, label="下抜けシグナル")
ax.set_title(f"{ticker_name} ボリンジャーバンド・ブレイクアウト戦略")
ax.legend(loc="upper left")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(f"{ticker_name}_bollinger.png", dpi=150)
plt.show()
plot_bollinger_with_signals(df, "6501_日立")
複数の製造メーカー銘柄でスクリーニング
「今日ブレイクアウトしている銘柄はどれか?」を一括チェックできるスクリーナーも作った。
# 製造メーカー銘柄リスト
manufacturer_tickers = {
"7203.T": "トヨタ",
"6501.T": "日立",
"6752.T": "パナソニック",
"6702.T": "富士通",
"6861.T": "キーエンス",
"7267.T": "ホンダ",
"6758.T": "ソニー",
"6902.T": "デンソー",
}
today_breakouts = []
for ticker, name in manufacturer_tickers.items():
try:
df = get_bollinger_bands(ticker, start="2024-01-01")
df = detect_breakout(df)
# 直近5日以内のブレイクアウト
recent = df.tail(5)
if recent["breakout_up"].any():
last_signal = recent[recent["breakout_up"]].index[-1]
today_breakouts.append({
"銘柄": name,
"コード": ticker,
"シグナル日": last_signal.strftime("%Y-%m-%d"),
"終値": df["Close"].iloc[-1],
"上限バンド": round(df["Upper"].iloc[-1], 0)
})
except Exception as e:
print(f"{name}: データ取得エラー ({e})")
if today_breakouts:
print("直近5日のブレイクアウト銘柄:")
print(pd.DataFrame(today_breakouts).to_string(index=False))
else:
print("直近5日のブレイクアウト銘柄なし")
実際にやってみての気づき
日立(6501.T)で3年分バックテストしてみたら、勝率は55〜60%、平均リターン1〜2%くらいだった。悪くはないけど「必勝法」でもない。特に、バンドが収縮している状態(スクイーズ)でのブレイクアウトは精度が高く、反対に「もともとボラが高い局面」でのシグナルは機能しにくい印象。
あと製造メーカーは決算前後の動きが大きいので、決算期(3月・9月)を外してバックテストすると数値が変わる。こういう「季節性」も考慮するのが次のステップかなと思ってる。
まとめ
ボリンジャーバンドのブレイクアウト戦略をPythonで実装すれば、「毎日チャートを眺める」作業を自動化できる。製造メーカー中心のポートフォリオを持っている人には相性がいい手法だと思う。
個人的に、スクリーナーを朝の通勤中にスマホで確認できるよう通知機能を追加したい。次は「スクイーズ検出 → ブレイクアウト待ち」の組み合わせにも挑戦してみる。

