※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。
Pythonで株式投資の自動化に取り組む際、最初に実装すべきロジックの一つが「ゴールデンクロスの検出」です。短期移動平均線が長期移動平均線を下から上に突き抜けた瞬間を「買いシグナル」と判定するこの手法は、テクニカル分析の中でも最も基本的かつ広く知られた売買シグナルです。
しかし、「移動平均線のクロスを検出する」という一見シンプルな処理にも、条件分岐のロジック設計やデータの前処理など、初心者がつまずきやすいポイントが複数存在します。
この記事では、yfinanceで取得した実際の株価データを使い、ゴールデンクロス(買いシグナル)とデッドクロス(売りシグナル)を検出するPythonコードを、コピペで動く完成形として提供します。
ゴールデンクロスとデッドクロスの基礎知識
コードの実装に入る前に、シグナルの定義を正確に理解しておく必要があります。
ゴールデンクロスの定義
ゴールデンクロスとは、短期移動平均線が長期移動平均線を下から上に突き抜ける現象を指します。これは「上昇トレンドへの転換」を示唆するシグナルとして解釈されます。
判定に必要な条件は以下の2つです。
- 前日時点: 短期移動平均線 ≦ 長期移動平均線(短期が下にある)
- 当日時点: 短期移動平均線 > 長期移動平均線(短期が上に抜けた)
この2つの条件が同時に成立した日が、ゴールデンクロス発生日となります。
デッドクロスの定義
デッドクロスはゴールデンクロスの逆で、短期移動平均線が長期移動平均線を上から下に突き抜ける現象です。「下降トレンドへの転換」を示唆する売りシグナルとして使われます。
移動平均線の期間設定
移動平均線の期間に絶対的な正解はありませんが、一般的に使用される組み合わせは以下のとおりです。
| 組み合わせ | 短期 | 長期 | 特徴 |
|---|---|---|---|
| 短期トレード向け | 5日 | 25日 | シグナル頻度が高い。ダマシも多い |
| 中期トレード向け | 25日 | 75日 | バランスが良く、最も一般的 |
| 長期投資向け | 50日 | 200日 | シグナル頻度が低い。信頼度は高い |
この記事では、最も汎用的な「25日・75日」の組み合わせを使用します。期間の変更はコード内の定数を書き換えるだけで対応できます。
実装の全体設計
コードを書き始める前に、処理の全体像を把握しておきます。
処理フローの概要
ゴールデンクロス判定ロジックの処理は、以下の5ステップで構成されます。
- yfinanceで株価データ(終値)を取得する
- 短期・長期の移動平均線を算出する
- 前日と当日の移動平均線の位置関係を比較する
- クロス発生日を特定し、シグナル列を付与する
- 結果を表示・CSV出力する
必要なライブラリ
以下のライブラリを使用します。事前にインストールしてください。
pip install yfinance pandas
- yfinance: Yahoo Financeから株価データを取得するオープンソースライブラリ
- pandas: データフレーム操作の標準ライブラリ
【コピペOK】ゴールデンクロス判定コードの完成形
以下が、ゴールデンクロスとデッドクロスを検出する完成コードです。
メインスクリプト
golden_cross.py として保存してください。
import yfinance as yf
import pandas as pd
# ==============================
# 設定エリア(ここを変更して使用)
# ==============================
SYMBOL = "7203.T" # 銘柄コード(トヨタ自動車)
PERIOD = "1y" # データ取得期間(1y=1年)
SHORT_WINDOW = 25 # 短期移動平均の期間
LONG_WINDOW = 75 # 長期移動平均の期間
OUTPUT_CSV = "signals.csv" # 出力ファイル名
# ==============================
# データ取得
# ==============================
def fetch_data(symbol: str, period: str) -> pd.DataFrame:
"""yfinanceから株価データを取得する"""
print(f"--- {symbol} の株価データを取得中 ---")
ticker = yf.Ticker(symbol)
df = ticker.history(period=period)
if df.empty:
raise ValueError("株価データを取得できませんでした。銘柄コードを確認してください。")
print(f"取得件数: {len(df)}日分")
return df
# ==============================
# 移動平均線の算出
# ==============================
def calc_moving_averages(df: pd.DataFrame) -> pd.DataFrame:
"""短期・長期の移動平均線を算出する"""
df[f"SMA_{SHORT_WINDOW}"] = df["Close"].rolling(window=SHORT_WINDOW).mean()
df[f"SMA_{LONG_WINDOW}"] = df["Close"].rolling(window=LONG_WINDOW).mean()
return df
# ==============================
# クロス判定ロジック(核心部分)
# ==============================
def detect_cross_signals(df: pd.DataFrame) -> pd.DataFrame:
"""ゴールデンクロスとデッドクロスを検出する"""
short_col = f"SMA_{SHORT_WINDOW}"
long_col = f"SMA_{LONG_WINDOW}"
# 前日の短期MAが長期MA以下 かつ 当日の短期MAが長期MAを超えた → ゴールデンクロス
golden_cross = (df[short_col].shift(1) <= df[long_col].shift(1)) &
(df[short_col] > df[long_col])
# 前日の短期MAが長期MA以上 かつ 当日の短期MAが長期MAを下回った → デッドクロス
dead_cross = (df[short_col].shift(1) >= df[long_col].shift(1)) &
(df[short_col] < df[long_col])
# シグナル列を作成
df["Signal"] = ""
df.loc[golden_cross, "Signal"] = "BUY"
df.loc[dead_cross, "Signal"] = "SELL"
return df
# ==============================
# 結果の表示と出力
# ==============================
def display_and_save(df: pd.DataFrame) -> None:
"""シグナル発生日を表示し、CSVに保存する"""
signals = df[df["Signal"] != ""].copy()
if signals.empty:
print("n指定期間内にクロスシグナルは検出されませんでした。")
else:
print(f"n--- 検出されたシグナル一覧 ---")
short_col = f"SMA_{SHORT_WINDOW}"
long_col = f"SMA_{LONG_WINDOW}"
for idx, row in signals.iterrows():
date_str = idx.strftime("%Y-%m-%d")
signal_type = "🟢 買い(GC)" if row["Signal"] == "BUY" else "🔴 売り(DC)"
print(
f" {date_str} | {signal_type} | "
f"終値: {row['Close']:.1f} | "
f"短期MA: {row[short_col]:.1f} | "
f"長期MA: {row[long_col]:.1f}"
)
print(f"n検出数: ゴールデンクロス {len(signals[signals['Signal'] == 'BUY'])}回 / "
f"デッドクロス {len(signals[signals['Signal'] == 'SELL'])}回")
# CSV出力
df.to_csv(OUTPUT_CSV)
print(f"n--- 全データをCSV出力しました: {OUTPUT_CSV} ---")
# ==============================
# メイン処理
# ==============================
def main():
df = fetch_data(SYMBOL, PERIOD)
df = calc_moving_averages(df)
df = detect_cross_signals(df)
display_and_save(df)
if __name__ == "__main__":
main()
実行方法
コマンドプロンプトまたはVS Codeのターミナルで以下を実行します。
python golden_cross.py
コードの詳細解説
各処理ブロックのロジックを詳しく解説します。
移動平均線の算出ロジック
pandasの rolling().mean() メソッドを使用しています。
df["SMA_25"] = df["Close"].rolling(window=25).mean()
この1行で、終値(Close)の直近25日間の平均値が各行に算出されます。最初の24行分はデータ不足のため NaN(欠損値)になりますが、これは正常な動作です。
クロス判定の条件分岐ロジック
クロス判定の核心は以下の条件式です。
golden_cross = (df[short_col].shift(1) <= df[long_col].shift(1)) &
(df[short_col] > df[long_col])
shift(1) は「1行前のデータ」を参照する関数です。これにより、以下の論理を実現しています。
| 条件 | 前日 | 当日 | 判定 |
|---|---|---|---|
| ゴールデンクロス | 短期MA ≦ 長期MA | 短期MA > 長期MA | BUY |
| デッドクロス | 短期MA ≧ 長期MA | 短期MA < 長期MA | SELL |
| それ以外 | — | — | シグナルなし |
shift(1)を使わずに当日のデータだけで判定すると、「短期MAが長期MAを上回っている期間すべて」がシグナルとして検出されてしまいます。前日との比較が「クロスした瞬間」を捉えるための必須条件です。
銘柄コードの変更方法
SYMBOL の値を変更するだけで、任意の日本株に対応できます。
SYMBOL = "9984.T" # ソフトバンクグループ
SYMBOL = "6758.T" # ソニーグループ
SYMBOL = "9432.T" # 日本電信電話(NTT)
日本株の場合は証券コードの末尾に .T(東証)を付けてください。
移動平均線の期間をカスタマイズする
短期トレード向けの設定
シグナルの発生頻度を高めたい場合は、短い期間を設定します。
SHORT_WINDOW = 5
LONG_WINDOW = 25
ただし、期間を短くするほど「ダマシ」(シグナル発生後すぐに逆方向に動く現象)が増加します。
長期投資向けの設定
シグナルの信頼度を高めたい場合は、長い期間を設定します。
SHORT_WINDOW = 50
LONG_WINDOW = 200
この設定ではシグナルの発生頻度が大幅に下がりますが、トレンド転換の信頼度は高くなります。
期間設定のトレードオフ
| 期間 | シグナル頻度 | ダマシの多さ | トレンド検出の遅延 |
|---|---|---|---|
| 短い(5/25) | 高い | 多い | 小さい |
| 中間(25/75) | 中程度 | 中程度 | 中程度 |
| 長い(50/200) | 低い | 少ない | 大きい |
最適な期間は銘柄や相場環境によって変わります。まずは25/75の標準設定で動作を確認し、バックテストの結果を見ながら調整するのが実践的なアプローチです。
【コピペOK】シグナル発生時にコンソール通知を出す応用版
基本版に加え、シグナル発生の有無を即座に確認できる「最新シグナルチェック機能」を追加した応用版です。
直近シグナル確認スクリプト
check_latest_signal.py として保存してください。
import yfinance as yf
import pandas as pd
# ==============================
# 設定エリア
# ==============================
WATCHLIST = ["7203.T", "9984.T", "6758.T", "9432.T", "8306.T"]
SHORT_WINDOW = 25
LONG_WINDOW = 75
def check_signal(symbol: str) -> dict:
"""指定銘柄の直近シグナルをチェックする"""
ticker = yf.Ticker(symbol)
df = ticker.history(period="6mo")
if df.empty or len(df) < LONG_WINDOW + 2:
return {"symbol": symbol, "status": "データ不足", "signal": "-"}
df["SMA_short"] = df["Close"].rolling(window=SHORT_WINDOW).mean()
df["SMA_long"] = df["Close"].rolling(window=LONG_WINDOW).mean()
df = df.dropna()
if len(df) < 2:
return {"symbol": symbol, "status": "データ不足", "signal": "-"}
# 直近2日分を取得
prev = df.iloc[-2]
curr = df.iloc[-1]
if prev["SMA_short"] <= prev["SMA_long"] and curr["SMA_short"] > curr["SMA_long"]:
signal = "🟢 BUY(ゴールデンクロス)"
elif prev["SMA_short"] >= prev["SMA_long"] and curr["SMA_short"] < curr["SMA_long"]:
signal = "🔴 SELL(デッドクロス)"
else:
signal = "— シグナルなし"
return {
"symbol": symbol,
"date": curr.name.strftime("%Y-%m-%d"),
"close": f"{curr['Close']:.1f}",
"signal": signal,
}
def main():
print("=== ウォッチリスト シグナルチェック ===n")
print(f"移動平均線: 短期{SHORT_WINDOW}日 / 長期{LONG_WINDOW}日n")
results = []
for symbol in WATCHLIST:
result = check_signal(symbol)
results.append(result)
print(f" {result['symbol']:>8} | {result.get('date', '-'):>10} | "
f"終値: {result.get('close', '-'):>10} | {result['signal']}")
print("n=== チェック完了 ===")
if __name__ == "__main__":
main()
このスクリプトを実行すると、ウォッチリストに登録した5銘柄について、直近の取引日にゴールデンクロスまたはデッドクロスが発生したかを一括で確認できます。
WATCHLIST の銘柄コードを書き換えれば、任意の銘柄セットに対応できます。
よくあるエラーと対処法
シグナルが1件も検出されない
以下の原因が考えられます。
- データ取得期間が短い:
PERIOD = "1y"を"2y"に延長してみてください - 移動平均期間が長すぎる: 50/200日設定では1年間にシグナルが発生しないことがあります
- 移動平均線が計算されていない:
NaNが除去されていない場合、比較が正常に行えません
df.dropna() を calc_moving_averages の後に入れることで、NaN行を除外できます。
SettingWithCopyWarningが表示される
pandasの警告で、動作には影響しませんが、以下のように .copy() を付けることで解消できます。
signals = df[df["Signal"] != ""].copy()
本記事のコードではすでにこの対策を組み込んでいます。
取得データの日付がずれている
yfinanceで取得されるデータのタイムゾーンは、市場ごとに異なります。日本株(.T)の場合はJST(日本標準時)ベースで取得されますが、日付の表示がUTCになるケースがあります。
表示上の問題であり、クロス判定のロジックには影響しません。日付を明示的にJSTに変換したい場合は以下を追加してください。
df.index = df.index.tz_convert("Asia/Tokyo")
まとめ
この記事では、ゴールデンクロスとデッドクロスを検出するPythonコードを、以下の構成で解説しました。
- ゴールデンクロスの定義: 前日に「短期MA ≦ 長期MA」、当日に「短期MA > 長期MA」が成立した瞬間
- 核心ロジック: pandasの
shift(1)を使って前日と当日の位置関係を比較する - 基本版コード: 単一銘柄のシグナル検出とCSV出力
- 応用版コード: 複数銘柄のウォッチリストを一括チェック
ゴールデンクロスは万能なシグナルではなく、レンジ相場ではダマシが多発します。次のステップとして、RSIやMACDなど複数の指標を組み合わせた「フィルタリングロジック」の実装に進むと、シグナルの精度を大幅に向上させることができます。

