※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。
Pythonで株価データを取得し、移動平均やRSIなど基本的なテクニカル指標を計算した経験はすでにお持ちではないでしょうか。
一目均衡表(Ichimoku Kinko Hyo)は、日本で生まれた世界的に利用されるテクニカル指標です。転換線・基準線・先行スパン・遅行スパンの5本の線が「時間」と「値幅」の両面から相場の均衡状態を可視化します。
しかし、いざPythonで実装しようとすると「先行スパンを26日先にシフトする処理」や「雲のねじれ判定ロジック」でつまずくケースが非常に多く見られます。
原因は、一目均衡表が他の指標と異なり「未来の日付にデータをプロットする」という特殊な構造を持つ点にあります。pandasのshift関数の正負の使い分けを誤ると、チャートがずれた状態で描画されてしまいます。
本記事では、一目均衡表の5本線すべてをpandasで正確に計算し、雲のねじれ検出と三役好転(Sanyaku Kouten)の自動判定までを一気通貫で実装します。
コードはコピペでそのまま動作します。銘柄コードと期間を差し替えれば、自分の監視銘柄にすぐ適用できる構成です。
一目均衡表の構成要素と理論
5本の線の計算ロジック
一目均衡表は以下の5つの要素で構成されます。
* 転換線(Tenkan-sen):過去9日間の最高値と最安値の中間値です。短期的な価格の均衡点を示します
* 基準線(Kijun-sen):過去26日間の最高値と最安値の中間値です。中期的な均衡点であり、トレンド方向の基準になります
* 先行スパン1(Senkou Span A):転換線と基準線の中間値を26日先にプロットしたものです
* 先行スパン2(Senkou Span B):過去52日間の最高値と最安値の中間値を26日先にプロットしたものです
* 遅行スパン(Chikou Span):当日の終値を26日前にプロットしたものです
先行スパン1と先行スパン2に囲まれた領域が「雲(Kumo)」と呼ばれます。雲は未来の支持帯・抵抗帯として機能します。
三役好転と三役逆転の条件
三役好転(強い買いシグナル)は、以下の3条件がすべて同時に成立した状態を指します。
* 転換線が基準線を上回っている(均衡表の好転)
* 現在の終値が雲の上に位置している(雲抜け)
* 遅行スパンが26日前の終値を上回っている(遅行スパンの好転)
三役逆転(強い売りシグナル)はこの3条件がすべて逆転した状態です。3条件の「同時成立」が重要であり、1つでも欠けていれば三役とは判定しません。
【コピペOK】一目均衡表の計算と三役好転判定コード
まず必要なライブラリをインストールしてください。
pip install yfinance pandas matplotlib
以下がメインコードです。
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
# ==============================
# 設定エリア
# ==============================
TICKER: str = "6758.T" # 銘柄コード(ソニーグループ)
START_DATE: str = "2023-06-01"
END_DATE: str = "2025-12-31"
TENKAN_PERIOD: int = 9 # 転換線の期間
KIJUN_PERIOD: int = 26 # 基準線の期間
SENKOU_B_PERIOD: int = 52 # 先行スパン2の期間
SHIFT_PERIOD: int = 26 # 先行スパン・遅行スパンのシフト日数
# ==============================
# データ取得
# ==============================
def fetch_stock_data(ticker: str, start: str, end: str) -> pd.DataFrame:
'株価データを取得して返す'
df: pd.DataFrame = yf.download(ticker, start=start, end=end, auto_adjust=True)
if df.empty:
raise ValueError(f"データを取得できませんでした: {ticker}")
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
return df
# ==============================
# 中間値計算ユーティリティ
# ==============================
def calc_midpoint(high: pd.Series, low: pd.Series, period: int) -> pd.Series:
'指定期間の最高値と最安値の中間値を計算する'
period_high: pd.Series = high.rolling(window=period).max()
period_low: pd.Series = low.rolling(window=period).min()
return (period_high + period_low) / 2
# ==============================
# 一目均衡表の全5線計算
# ==============================
def calc_ichimoku(df: pd.DataFrame) -> pd.DataFrame:
'一目均衡表の5本線を計算してカラムに追加する'
# 転換線(過去9日の中間値)
df["Tenkan"] = calc_midpoint(df["High"], df["Low"], TENKAN_PERIOD)
# 基準線(過去26日の中間値)
df["Kijun"] = calc_midpoint(df["High"], df["Low"], KIJUN_PERIOD)
# 先行スパン1(転換線と基準線の中間値を26日先へシフト)
df["Senkou_A"] = ((df["Tenkan"] + df["Kijun"]) / 2).shift(SHIFT_PERIOD)
# 先行スパン2(過去52日の中間値を26日先へシフト)
df["Senkou_B"] = calc_midpoint(df["High"], df["Low"], SENKOU_B_PERIOD).shift(SHIFT_PERIOD)
# 遅行スパン(当日終値を26日前へシフト)
df["Chikou"] = df["Close"].shift(-SHIFT_PERIOD)
return df
# ==============================
# 雲のねじれ検出
# ==============================
def detect_kumo_twist(df: pd.DataFrame) -> pd.DataFrame:
'先行スパン1と先行スパン2の交差(雲のねじれ)を検出する'
prev_diff: pd.Series = (df["Senkou_A"] - df["Senkou_B"]).shift(1)
curr_diff: pd.Series = df["Senkou_A"] - df["Senkou_B"]
df["Kumo_Twist"] = (prev_diff * curr_diff) < 0
return df
# ==============================
# 三役好転・三役逆転の判定
# ==============================
def detect_sanyaku(df: pd.DataFrame) -> pd.DataFrame:
'三役好転と三役逆転をそれぞれ判定する'
cloud_top: pd.Series = df[["Senkou_A", "Senkou_B"]].max(axis=1)
cloud_bottom: pd.Series = df[["Senkou_A", "Senkou_B"]].min(axis=1)
# 遅行スパン比較用:26日前の終値
close_26_ago: pd.Series = df["Close"].shift(SHIFT_PERIOD)
# 三役好転条件
cond_tenkan_above: pd.Series = df["Tenkan"] > df["Kijun"]
cond_above_cloud: pd.Series = df["Close"] > cloud_top
cond_chikou_above: pd.Series = df["Chikou"] > close_26_ago
# 三役逆転条件
cond_tenkan_below: pd.Series = df["Tenkan"] < df["Kijun"]
cond_below_cloud: pd.Series = df["Close"] < cloud_bottom
cond_chikou_below: pd.Series = df["Chikou"] < close_26_ago
df["Sanyaku"] = "NEUTRAL"
df.loc[
cond_tenkan_above & cond_above_cloud & cond_chikou_above, "Sanyaku"
] = "BULLISH"
df.loc[
cond_tenkan_below & cond_below_cloud & cond_chikou_below, "Sanyaku"
] = "BEARISH"
return df
# ==============================
# シグナル一覧表示
# ==============================
def print_signals(df: pd.DataFrame) -> None:
'三役好転・逆転および雲のねじれをコンソール出力する'
# 三役の状態変化日のみ抽出
df["Sanyaku_Change"] = df["Sanyaku"] != df["Sanyaku"].shift(1)
changes: pd.DataFrame = df[df["Sanyaku_Change"] & (df["Sanyaku"] != "NEUTRAL")]
print("=== 三役好転 / 三役逆転シグナル ===")
if changes.empty:
print("三役シグナルは検出されませんでした。")
else:
for idx, row in changes.iterrows():
date_str: str = idx.strftime("%Y-%m-%d") if hasattr(idx, "strftime") else str(idx)
print(
f"{date_str} | 終値: {row['Close']:>10.1f} | "
f"転換線: {row['Tenkan']:>10.1f} | "
f"基準線: {row['Kijun']:>10.1f} | "
f"判定: {row['Sanyaku']}"
)
# 雲のねじれ
twists: pd.DataFrame = df[df["Kumo_Twist"]]
print(f"n=== 雲のねじれ検出: {len(twists)}件 ===")
for idx, row in twists.head(10).iterrows():
date_str = idx.strftime("%Y-%m-%d") if hasattr(idx, "strftime") else str(idx)
direction: str = "上昇雲へ" if row["Senkou_A"] > row["Senkou_B"] else "下降雲へ"
print(f"{date_str} | {direction}")
# ==============================
# チャート描画
# ==============================
def plot_ichimoku(df: pd.DataFrame, ticker: str) -> None:
'一目均衡表の全要素を1枚のチャートに描画する'
fig, ax = plt.subplots(figsize=(16, 9))
# 株価
ax.plot(df.index, df["Close"], label="Close", color="black", linewidth=1.0)
# 転換線・基準線
ax.plot(df.index, df["Tenkan"], label="Tenkan (9)", color="blue", linewidth=0.7)
ax.plot(df.index, df["Kijun"], label="Kijun (26)", color="red", linewidth=0.7)
# 遅行スパン
ax.plot(df.index, df["Chikou"], label="Chikou", color="green", linewidth=0.7, linestyle="--")
# 雲(先行スパン1と先行スパン2の間を塗りつぶし)
ax.fill_between(
df.index, df["Senkou_A"], df["Senkou_B"],
where=df["Senkou_A"] >= df["Senkou_B"],
color="lightcoral", alpha=0.3, label="Cloud (bullish)",
)
ax.fill_between(
df.index, df["Senkou_A"], df["Senkou_B"],
where=df["Senkou_A"] < df["Senkou_B"],
color="lightblue", alpha=0.3, label="Cloud (bearish)",
)
# 三役マーカー
bullish: pd.DataFrame = df[
(df["Sanyaku"] == "BULLISH") & df["Sanyaku_Change"]
]
bearish: pd.DataFrame = df[
(df["Sanyaku"] == "BEARISH") & df["Sanyaku_Change"]
]
ax.scatter(bullish.index, bullish["Close"], marker="^", color="lime", s=120, zorder=5, label="Sanyaku Bullish")
ax.scatter(bearish.index, bearish["Close"], marker="v", color="magenta", s=120, zorder=5, label="Sanyaku Bearish")
ax.set_title(f"{ticker} - Ichimoku Kinko Hyo")
ax.set_ylabel("Price (JPY)")
ax.set_xlabel("Date")
ax.legend(loc="upper left", fontsize=8)
ax.grid(True, alpha=0.3)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
fig.autofmt_xdate()
plt.tight_layout()
plt.savefig("ichimoku_chart.png", dpi=150)
plt.show()
print("チャートを ichimoku_chart.png に保存しました。")
# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
df: pd.DataFrame = fetch_stock_data(TICKER, START_DATE, END_DATE)
df = calc_ichimoku(df)
df = detect_kumo_twist(df)
df = detect_sanyaku(df)
print_signals(df)
plot_ichimoku(df, TICKER)
コードの処理フロー解説
上記のコードは、以下の6ステップで構成されています。
* データ取得:yfinanceで指定銘柄のOHLCデータをダウンロードし、DataFrameに格納します
* 中間値計算:rolling().max()とrolling().min()で各期間の最高値・最安値を取得し、その平均を中間値として返すユーティリティ関数を使います
* 一目均衡表計算:転換線・基準線・先行スパン1/2・遅行スパンの5本をすべてDataFrameのカラムとして追加します。先行スパンはshift(26)で未来方向へ、遅行スパンはshift(-26)で過去方向へシフトします
* 雲のねじれ検出:先行スパン1と先行スパン2の差分の符号が前日と反転した地点をTrue/Falseで記録します
* 三役判定:転換線と基準線の位置関係、終値と雲の位置関係、遅行スパンと過去終値の位置関係の3条件を同時に評価します
* 可視化:fill_betweenで雲を塗りつぶし、三役シグナルの発生日をマーカーで強調した1枚チャートを描画・保存します
TENKAN_PERIODやKIJUN_PERIODを変更すれば、週足用(転換線7・基準線22など)の設定にも対応できます。
シグナルの読み解き方と実践的な運用知識
雲の厚みが示す支持・抵抗の強さ
雲の厚みは支持帯・抵抗帯の強度を視覚的に表現しています。厚い雲を株価がブレイクするには大きなエネルギーが必要であり、薄い雲は容易に突破されます。
実務的には、先行スパン1と先行スパン2の差分が終値の2%未満であれば「薄い雲」と判断してください。薄い雲の付近では、ブレイク後に急加速しやすい傾向があります。
遅行スパンの確認を省略しない
三役好転を判定する際に、遅行スパンの条件を省略してしまうケースが散見されます。遅行スパンは「現在の終値が26日前の相場と比較して優位かどうか」を示す重要な要素です。
遅行スパンの条件を外した判定は三役好転ではありません。2条件だけで売買判断を行うと、ダマシに巻き込まれる確率が大幅に上がります。
【コピペOK】雲の厚み分析と週次レポート出力の発展版
以下は、雲の厚みをスコア化し、週次でサマリーを出力する発展版コードです。メインコードと同じファイルに追記して使用してください。
# ==============================
# 設定エリア(発展版追加分)
# ==============================
CLOUD_THIN_THRESHOLD: float = 0.02 # 薄い雲の閾値(終値比2%)
# ==============================
# 雲の厚み分析
# ==============================
def analyze_cloud_thickness(df: pd.DataFrame) -> pd.DataFrame:
'雲の厚みを終値比率で算出しカテゴリを付与する'
df["Cloud_Thickness"] = abs(df["Senkou_A"] - df["Senkou_B"])
df["Cloud_Ratio"] = df["Cloud_Thickness"] / df["Close"]
df["Cloud_Category"] = "NORMAL"
df.loc[df["Cloud_Ratio"] < CLOUD_THIN_THRESHOLD, "Cloud_Category"] = "THIN"
df.loc[df["Cloud_Ratio"] >= CLOUD_THIN_THRESHOLD * 3, "Cloud_Category"] = "THICK"
return df
# ==============================
# 週次サマリーレポート
# ==============================
def generate_weekly_report(df: pd.DataFrame) -> None:
'週ごとに三役状態・雲の厚みをサマリー出力する'
df_valid: pd.DataFrame = df.dropna(subset=["Tenkan", "Kijun", "Senkou_A", "Senkou_B"]).copy()
df_valid["Week"] = df_valid.index.to_period("W")
print("n=== 週次一目均衡表レポート ===")
for week, group in df_valid.groupby("Week"):
last_row: pd.Series = group.iloc[-1]
cloud_dir: str = "上昇雲" if last_row["Senkou_A"] > last_row["Senkou_B"] else "下降雲"
print(
f"{week} | 終値: {last_row['Close']:>10.1f} | "
f"三役: {last_row['Sanyaku']:>8s} | "
f"雲: {cloud_dir} ({last_row['Cloud_Category']}) | "
f"雲厚比: {last_row['Cloud_Ratio']:.3f}"
)
# ==============================
# 発展版メイン処理
# ==============================
if __name__ == "__main__":
df = fetch_stock_data(TICKER, START_DATE, END_DATE)
df = calc_ichimoku(df)
df = detect_kumo_twist(df)
df = detect_sanyaku(df)
df = analyze_cloud_thickness(df)
generate_weekly_report(df)
コードの処理フロー解説
上記のコードは、以下の3ステップで構成されています。
* 雲厚み分析:先行スパン1と先行スパン2の差分を終値で割り、相対的な厚みをTHIN/NORMAL/THICKの3カテゴリに分類します
* 週次集約:to_period("W")で週単位にグルーピングし、各週の最終営業日のデータを取り出します
* レポート出力:三役の判定結果、雲の方向、雲の厚みカテゴリを1行にまとめて週次レポートとしてコンソールに出力します
to_period("M")に変更すれば月次レポートへの切り替えも即座に対応できます。
よくあるエラーと対処法
雲が正しい位置に描画されない
先行スパンのshiftの方向を間違えていることが原因です。先行スパンは「未来にプロットする」ため、shift(26)(正の値)で右方向にずらします。shift(-26)にすると過去方向にずれてしまいます。
以下を試してください。
* Senkou_Aのシフトがshift(SHIFT_PERIOD)(正の値)であることを確認する
* チャートの右端に雲が26日分はみ出して描画されているか目視で確認する
* DataFrameの末尾26行でSenkou_AがNaNになっていないことをdf.tail(30)で確認する
遅行スパンが途中で途切れる
遅行スパンはshift(-26)で26日前にプロットするため、DataFrameの末尾26日分はNaN(欠損値)になります。これは仕様上正常な挙動であり、エラーではありません。
描画時に途切れて見えるのが気になる場合は、END_DATEを現在日よりも1〜2ヶ月先に設定してください。yfinanceは存在するデータの範囲のみを返すため、将来日付を指定しても問題は発生しません。
三役好転が一度も検出されない
三役好転は3条件の同時成立が必要であり、出現頻度が低い強力なシグナルです。分析期間が短いと検出されないことは珍しくありません。
以下を試してください。
* 分析期間を最低2年以上に延長する
* detect_sanyaku関数内の各条件を個別にprintし、どの条件が成立していないか特定する
* まずは2条件(転換線>基準線 かつ 雲抜け)で部分検索し、遅行スパン条件がボトルネックかどうかを確認する
まとめ
この記事では、一目均衡表の5本線の計算ロジックからPython実装、三役好転の自動判定、雲の厚み分析による週次レポート出力までを解説しました。
要点を整理します。
* 一目均衡表は転換線・基準線・先行スパン1/2・遅行スパンの5要素で構成され、それぞれ異なる期間の中間値をベースに計算します
* 先行スパンはshift(26)で未来方向へ、遅行スパンはshift(-26)で過去方向へシフトする点が他の指標にはない特殊な処理です
* 三役好転は「転換線>基準線」「終値>雲上限」「遅行スパン>26日前終値」の3条件同時成立であり、1つでも欠ければ三役とは判定しません
* 雲の厚みは支持・抵抗の強度を示し、終値比2%未満の薄い雲付近ではブレイク後の急変動に注意が必要です
* 設定エリアの期間パラメータを変更するだけで日足以外の時間軸への応用が可能です
次のステップとして、複数銘柄を一括スキャンし「三役好転が発生した銘柄リスト」を毎朝自動出力するスクリーニングツールへの拡張を検討してください。TICKERをリスト化してループ処理に変更し、結果をCSVに書き出すだけで実装できます。
さらに、MACDやRSIとの複合条件を加えることで、一目均衡表単体では捉えきれないモメンタム(Momentum:相場の勢い)の変化も組み合わせた多角的な分析基盤を構築できます。

