Scriptone Scriptone

[Pythonによる株式分析]Polarsで一目均衡表の計算をし表示する

blog-image

概要

前回の記事ではボリンジャーバンドによる価格変動範囲の把握方法について解説しました。今回は、日本で開発されたテクニカル指標である一目均衡表について、Polarsを使った計算方法とPlotlyでの可視化方法を解説します。

一目均衡表は、転換線、基準線、先行スパン1・2、遅行スパンの5つの要素から構成され、複数の時間軸を同時に分析できる強力な指標です。

モチベーション

一目均衡表(いちもくきんこうひょう)は、1936年に日本の細田悟一氏によって開発されたテクニカル指標です。海外でも「Ichimoku Cloud(一目の雲)」として知られており、日本国外のトレーダーにも利用されています。一目均衡表の主な特徴は以下の通りです。

  • 複数の時間軸を同時に分析: 短期(9日)、中期(26日)、長期(52日)のトレンドを一度に把握できる
  • 雲によるサポート・レジスタンスの可視化: 先行スパン1と2の間にできる「雲」が、将来のサポートラインやレジスタンスラインとして機能
  • トレンドの方向性と強さを把握: 価格と雲の位置関係、転換線と基準線のクロスなどから、トレンドの強さや転換点を判断できる
  • 視覚的に分かりやすい: チャート上で雲が明確に表示されるため、トレンドの方向性が一目で分かる

前回同様、PythonのモダンなライブラリであるPolars、Plotly、marimo、yfinance-plを使って分析を進めていきます。

使用するライブラリ

ライブラリは以下の通りです。

ライブラリ関連ライブラリ説明
polarspandasデータフレームを高速に扱えるライブラリ
numpy-数値計算ライブラリで、配列操作や差分計算(np.diff())を使って雲の陰転・陽転のクロスオーバーポイントを検出する
plotlymatplotlib可視化ライブラリでユーザーがインタラクティブにグラフを操作できる
marimojupyterpy形式で動くノートブックでインタラクティブにコードを動かすことができる
yfinance-plyfinanceyfinance-rsをラップして作ったPython向けのライブラリでPolarsでYahoo Financeの情報を扱える

ノートとコード

marimoのノートのHTML版はこちらにあります。GitHub上のPython形式のコードにもリンクします

解説

一目均衡表とは

一目均衡表は、以下の5つの要素から構成されるテクニカル指標です。

  1. 転換線(Conversion Line / 転換線): 過去9日間の最高値と最安値の平均
  2. 基準線(Base Line / 基準線): 過去26日間の最高値と最安値の平均
  3. 先行スパン1(Leading Span A / 先行スパン1): (転換線 + 基準線) / 2 を26日未来にシフト
  4. 先行スパン2(Leading Span B / 先行スパン2): 過去52日間の最高値と最安値の平均を26日未来にシフト
  5. 遅行スパン(Lagging Span / 遅行スパン): 当日の終値を26日過去にシフト

先行スパン1と先行スパン2の間の領域が「雲(Kumo / Cloud)」と呼ばれ、この雲がサポートラインやレジスタンスラインとして機能します。

未来の雲: 先行スパンは26日未来にシフトされているため、データの最後の26日分は未来の日付に対応します。この未来部分を表示することで、将来のサポート・レジスタンスを先読みすることができます。これが一目均衡表の最大の特徴の一つです。

一目均衡表の計算方法

Polarsを使って一目均衡表の各要素を計算します。rolling_max()rolling_min()を使って期間内の最高値・最安値を取得し、shift()で時間軸をずらします。

import polars as pl
from typing import TypedDict


class IchimokuValues(TypedDict):
    conversion_line: pl.Series
    base_line: pl.Series
    leading_span1: pl.Series
    leading_span2: pl.Series
    lagging_span: pl.Series


def get_ichimoku_values(df: pl.DataFrame) -> IchimokuValues:
    # Decimal型はrolling操作がサポートされていないため、先にFloat64に変換
    high = df["high.amount"].cast(pl.Float64)
    low = df["low.amount"].cast(pl.Float64)
    close = df["close.amount"].cast(pl.Float64)

    # 転換線: 過去9日間の (Max + Min) / 2
    conversion_line = (high.rolling_max(9) + low.rolling_min(9)) / 2

    # 基準線: 過去26日間の (Max + Min) / 2
    base_line = (high.rolling_max(26) + low.rolling_min(26)) / 2

    # 先行スパン1: (転換線 + 基準線) / 2 を26日未来にずらす
    # Polarsのshiftはデフォルトで空いた部分をnullで埋めます
    leading_span1 = ((conversion_line + base_line) / 2).shift(26)

    # 先行スパン2: 過去52日間の (Max + Min) / 2 を26日未来にずらす
    leading_span2 = ((high.rolling_max(52) + low.rolling_min(52)) / 2).shift(26)

    # 遅行スパン: 今日の終値を26日過去にずらす
    lagging_span = close.shift(-26)

    return {
        "conversion_line": conversion_line,
        "base_line": base_line,
        "leading_span1": leading_span1,
        "leading_span2": leading_span2,
        "lagging_span": lagging_span,
    }

いくつか注意点があります。

  • Decimal型の変換: yfinance-plで取得したデータはDecimal型ですが、Polarsのrolling_max()rolling_min()はDecimal型をサポートしていないため、cast(pl.Float64)で変換します
  • shift()の方向: shift()は正の値で未来方向、負の値で過去方向にシフトします。先行スパンはshift(26)で26日未来に、遅行スパンはshift(-26)で26日過去にシフトします
  • 期間の意味: 9日、26日、52日という期間は、一目均衡表の標準的な設定値です。これらは日本の市場の営業日に基づいて設定されています

一目均衡表の基本的な見方

一目均衡表は視覚的に分かりやすい指標ですが、いくつかの基本的な見方があります。

雲(先行スパン1と2の間)

雲は将来のサポートラインやレジスタンスラインとして機能します。

  • 価格が雲の上にある: 上昇トレンド。雲がサポートラインとして機能する可能性が高い
  • 価格が雲の中にある: レンジ相場。方向性が定まっていない
  • 価格が雲の下にある: 下降トレンド。雲がレジスタンスラインとして機能する可能性が高い

また、雲の厚さもトレンドの強さを示します。雲が厚いほど、サポート・レジスタンスとしての強さが増します。

雲の陰転と陽転

雲の色は、先行スパン1と先行スパン2の位置関係によって変わります。

  • 陽転(強気の雲): 先行スパン1が先行スパン2を上回っている状態。上昇トレンドを示唆
  • 陰転(弱気の雲): 先行スパン1が先行スパン2を下回っている状態。下降トレンドを示唆

陰転から陽転に変わるタイミング、またはその逆のタイミングは、トレンド転換の重要なシグナルとなります。

転換線と基準線のクロス

転換線(短期線)が基準線(中期線)を上抜けると買いシグナル、下抜けると売りシグナルとされます。移動平均線のゴールデンクロス・デッドクロスと同様の考え方です。

遅行スパン

遅行スパンは、現在の価格と26日前の価格を比較するための指標です。遅行スパンが過去の価格を上回っている場合は強気、下回っている場合は弱気と判断されます。

未来の雲の活用

未来の雲は、先行スパンが26日先にシフトされているため表示される将来のサポート・レジスタンスです。

  • 厚い未来の雲: 強力なサポート/レジスタンスが予想される。突破が困難な可能性
  • 薄い未来の雲: サポート/レジスタンスが弱い。価格が突破しやすい可能性
  • 未来の雲のねじれ: 陰転⇔陽転が切り替わるポイント。トレンド転換の可能性を示唆

未来の雲はあくまで現在のデータに基づく予測であり、実際の価格動向によって変化します。定期的にチャートを更新して確認することが重要です。

Plotlyでの可視化

Plotlyを使って、ローソク足と一目均衡表の5つの要素を表示します。特に、先行スパン1と2の間を塗りつぶして「雲」を可視化し、陰転・陽転に応じて色を変えます。また、未来の雲も表示して将来のサポート・レジスタンスを視覚化します。

未来の日付を生成する関数

一目均衡表の先行スパンは26日未来にシフトされているため、未来26日分の雲を表示するには未来の日付が必要です。Pythonのdatetimeで営業日ベースの未来の日付を生成します。

from datetime import datetime, timedelta

def generate_future_business_days(last_date: str, n_days: int = 26) -> list[str]:
    """
    最後の日付から未来の営業日(平日)を生成する

    Args:
        last_date: 最後のデータの日付(文字列形式: "YYYY-MM-DD")
        n_days: 生成する営業日数(デフォルト26日)

    Returns:
        未来の営業日のリスト(文字列形式: "YYYY-MM-DD")
    """
    # 最後の日付を日付オブジェクトに変換
    current = datetime.strptime(last_date, "%Y-%m-%d")
    future_dates = []

    # 必要な営業日数が揃うまで1日ずつ進める
    while len(future_dates) < n_days:
        current += timedelta(days=1)
        # 営業日(月〜金)のみ追加(weekday(): 月=0, 金=4)
        if current.weekday() < 5:
            future_dates.append(current.strftime("%Y-%m-%d"))

    return future_dates

この関数のポイント:

  • Pythonのdatetime: datetime.strptime()で文字列を日付に変換し、timedelta(days=1)で1日ずつ進める
  • 営業日のフィルタリング: weekday()で曜日を取得(月=0, 火=1, …, 金=4, 土=5, 日=6)し、5未満(月〜金)のみ追加
  • 必要な日数まで繰り返し: 土日をスキップしながら、26営業日分が揃うまでループ

雲の陰転・陽転を色分けする関数

まず、雲をセグメントごとに分割して色分けする関数を定義します。

import numpy as np
import plotly.graph_objects as go

def create_cloud_segments(dates, span1, span2):
    """
    一目均衡表の雲を陰転・陽転に応じて色分けしたセグメントに分割する
    """
    # Polars SeriesをNumPy配列に変換
    if hasattr(span1, "to_numpy"):
        span1 = span1.to_numpy()
    if hasattr(span2, "to_numpy"):
        span2 = span2.to_numpy()

    # 陽転判定(先行スパン1 > 先行スパン2)
    is_bullish = np.nan_to_num(span1 > span2, nan=False)

    # クロスオーバーポイントを検出(陰転⇔陽転が切り替わる箇所)
    crossovers = np.where(np.diff(is_bullish.astype(int)) != 0)[0] + 1

    # セグメントの境界を作成
    boundaries = np.concatenate([[0], crossovers, [len(dates)]])

    traces = []

    # 各セグメントごとに処理
    for i in range(len(boundaries) - 1):
        start_idx = boundaries[i]
        end_idx = boundaries[i + 1]

        segment_dates = dates[start_idx:end_idx]
        segment_span1 = span1[start_idx:end_idx]
        segment_span2 = span2[start_idx:end_idx]

        # NaNのみのセグメントはスキップ
        if np.all(np.isnan(segment_span1)) or np.all(np.isnan(segment_span2)):
            continue

        # セグメントの陽転・陰転を判定して色を設定
        segment_is_bullish = is_bullish[start_idx]
        fillcolor = (
            "rgba(135, 206, 250, 0.3)"  # 陽転: ライトブルー
            if segment_is_bullish
            else "rgba(255, 165, 0, 0.3)"  # 陰転: オレンジ
        )

        # 先行スパン1のライン(塗りつぶしなし)
        traces.append(
            go.Scatter(
                x=segment_dates,
                y=segment_span1,
                mode="lines",
                line=dict(width=0.5, color="green"),
                showlegend=False,
                hoverinfo="skip",
            )
        )

        # 先行スパン2のライン(先行スパン1との間を塗りつぶし)
        traces.append(
            go.Scatter(
                x=segment_dates,
                y=segment_span2,
                mode="lines",
                line=dict(width=0.5, color="orange"),
                fill="tonexty",  # 前のトレース(先行スパン1)との間を塗りつぶし
                fillcolor=fillcolor,
                showlegend=False,
                hoverinfo="skip",
            )
        )

    return traces

この関数のポイントは以下の通りです。

  • np.diff()でクロスオーバー検出: 陰転・陽転が切り替わるポイントを自動検出
  • セグメント分割: クロスオーバーポイントで雲を分割し、各セグメントごとに色を設定
  • 色の使い分け: 陽転時はライトブルー、陰転時はオレンジで視覚的に区別
  • fill="tonexty": 先行スパン1と2の間を塗りつぶして雲を表現

未来の雲を含む雲のセグメント作成

未来の雲を表示するため、create_cloud_segments()関数を拡張します。

def create_cloud_segments_with_future(dates, span1, span2, num_future_days=26):
    """
    一目均衡表の雲(未来の雲を含む)を陰転・陽転に応じて色分けしたセグメントに分割する
    """
    # 未来の日付を生成
    last_date = dates[-1]
    future_dates = generate_future_business_days(last_date, num_future_days)

    # 日付を結合(過去 + 未来)
    all_dates = dates + future_dates

    # Polars SeriesをNumPy配列に変換
    if hasattr(span1, "to_numpy"):
        span1_values = span1.to_numpy()
    else:
        span1_values = span1
    if hasattr(span2, "to_numpy"):
        span2_values = span2.to_numpy()
    else:
        span2_values = span2

    # 先行スパンの配列を拡張(先頭26個をNaNで埋める)
    span1_extended = np.concatenate([[np.nan] * num_future_days, span1_values])
    span2_extended = np.concatenate([[np.nan] * num_future_days, span2_values])

    # 過去部分と未来部分を分離
    past_dates = dates
    future_span1 = span1_extended[-num_future_days:]
    future_span2 = span2_extended[-num_future_days:]
    past_span1 = span1_extended[:-num_future_days]
    past_span2 = span2_extended[:-num_future_days]

    # 過去部分の陽転判定
    is_bullish = np.nan_to_num(past_span1 > past_span2, nan=False)

    # クロスオーバーポイントを検出
    crossovers = np.where(np.diff(is_bullish.astype(int)) != 0)[0] + 1

    # セグメントの境界を作成
    boundaries = np.concatenate([[0], crossovers, [len(past_dates)]])

    traces = []
    last_segment_color = None

    # 過去部分の各セグメントを処理
    for i in range(len(boundaries) - 1):
        start_idx = boundaries[i]
        end_idx = boundaries[i + 1]

        segment_dates = past_dates[start_idx:end_idx]
        segment_span1 = past_span1[start_idx:end_idx]
        segment_span2 = past_span2[start_idx:end_idx]

        if np.all(np.isnan(segment_span1)) or np.all(np.isnan(segment_span2)):
            continue

        # セグメントの陽転・陰転を判定して色を設定
        segment_is_bullish = is_bullish[start_idx]
        fillcolor = (
            "rgba(135, 206, 250, 0.3)"  # 陽転: ライトブルー
            if segment_is_bullish
            else "rgba(255, 165, 0, 0.3)"  # 陰転: オレンジ
        )
        last_segment_color = fillcolor

        # 先行スパン1のライン
        traces.append(
            go.Scatter(
                x=segment_dates,
                y=segment_span1,
                mode="lines",
                line=dict(width=0.5, color="green"),
                showlegend=False,
                hoverinfo="skip",
            )
        )

        # 先行スパン2のライン
        traces.append(
            go.Scatter(
                x=segment_dates,
                y=segment_span2,
                mode="lines",
                line=dict(width=0.5, color="orange"),
                fill="tonexty",
                fillcolor=fillcolor,
                showlegend=False,
                hoverinfo="skip",
            )
        )

    # 未来の雲を追加(最後のセグメントの色を継続)
    if last_segment_color and not np.all(np.isnan(future_span1)):
        traces.append(
            go.Scatter(
                x=future_dates,
                y=future_span1,
                mode="lines",
                line=dict(width=0.5, color="green", dash="dot"),  # 点線で未来を示す
                showlegend=False,
                hoverinfo="skip",
            )
        )

        traces.append(
            go.Scatter(
                x=future_dates,
                y=future_span2,
                mode="lines",
                line=dict(width=0.5, color="orange", dash="dot"),  # 点線で未来を示す
                fill="tonexty",
                fillcolor=last_segment_color,  # 最後のセグメントの色を継続
                showlegend=False,
                hoverinfo="skip",
            )
        )

    return traces

この関数のポイント:

  • 未来の日付生成: generate_future_business_days()で26営業日分の未来の日付を生成
  • 配列の拡張: np.concatenate([[np.nan] * 26, span1_values])で先頭26個をnp.nanで埋め、その後に実際の先行スパン値を連結
  • 過去と未来で分離: 拡張された配列の過去部分([:-26])で陰転・陽転を判定し、未来部分([-26:])は最後のセグメントの色を継続
  • 点線で未来を表現: dash="dot"で未来の雲を点線表示し、過去のデータと視覚的に区別

チャートの作成

次に、データを取得して一目均衡表のチャートを作成します。

import polars as pl
import yfinance_pl as yf

# データ取得
ticker = yf.Ticker("8381.T")
hist = ticker.history(period="6mo")

# 一目均衡表の計算
ichimoku = get_ichimoku_values(hist)

# DataFrameに追加
df_plot = hist.with_columns(
    conversion_line=ichimoku["conversion_line"],
    base_line=ichimoku["base_line"],
    leading_span1=ichimoku["leading_span1"],
    leading_span2=ichimoku["leading_span2"],
    lagging_span=ichimoku["lagging_span"],
).with_columns(
    [
        pl.col("open.amount").cast(pl.Float64),
        pl.col("high.amount").cast(pl.Float64),
        pl.col("low.amount").cast(pl.Float64),
        pl.col("close.amount").cast(pl.Float64),
    ]
)

dates = df_plot["date"].to_list()

# チャート作成
data = []

# ローソク足
data.append(
    go.Candlestick(
        x=dates,
        open=df_plot["open.amount"],
        high=df_plot["high.amount"],
        low=df_plot["low.amount"],
        close=df_plot["close.amount"],
        increasing_line_color="red",
        decreasing_line_color="green",
        name="株価",
    )
)

# 転換線
data.append(
    go.Scatter(
        x=dates,
        y=df_plot["conversion_line"],
        mode="lines",
        name="転換線",
        line=dict(color="blue", width=1),
    )
)

# 基準線
data.append(
    go.Scatter(
        x=dates,
        y=df_plot["base_line"],
        mode="lines",
        name="基準線",
        line=dict(color="red", width=1),
    )
)

# 雲(陰転・陽転の色分け + 未来の雲)
cloud_traces = create_cloud_segments_with_future(
    dates,
    df_plot["leading_span1"],
    df_plot["leading_span2"],
    num_future_days=26,
)
data.extend(cloud_traces)

# 遅行スパン
data.append(
    go.Scatter(
        x=dates,
        y=df_plot["lagging_span"],
        mode="lines",
        name="遅行スパン",
        line=dict(color="purple", width=1, dash="dot"),
    )
)

# レイアウト設定
layout = go.Layout(
    title="株価と一目均衡表",
    xaxis_title="日付",
    yaxis_title="価格",
    xaxis_rangeslider_visible=False,
)

fig = go.Figure(data=data, layout=layout)
fig.show()

このコードでは、create_cloud_segments()関数で雲を陰転・陽転に応じて色分けしています。陽転時はライトブルー、陰転時はオレンジで表示されるため、トレンドの変化が視覚的に分かりやすくなります。

このコードのポイントは以下の通りです。

  • 雲の陰転・陽転を自動検出: create_cloud_segments_with_future()関数が、先行スパン1と2の大小関係を判定してクロスオーバーポイントを検出
  • セグメントごとの色分け: 陽転時はライトブルー、陰転時はオレンジで雲を塗りつぶし、トレンドの変化を視覚的に表現
  • 効率的な描画: NumPyのnp.diff()を使ってクロスオーバーを検出し、必要最小限のトレース数で雲を描画
  • 色の透明度: rgba()形式で透明度30%を設定し、ローソク足や他のラインが見やすいように配慮
  • 未来の雲の表示: Pythonのdatetimeで26営業日分の未来の日付を生成し、np.concatenate()で先行スパンの配列を拡張して未来の雲を表示
  • 配列の拡張: 先頭26個をnp.nanで埋めることで、未来の日付に先行スパンの実際の値をマッピング
  • 点線で未来を区別: 未来の雲はdash="dot"で点線表示し、過去のデータと明確に区別
  • 色の継続: 過去部分の最後のセグメント(陰転/陽転)の色を未来の雲にも適用し、トレンドの継続性を視覚化

まとめ

今回は一目均衡表の5つの要素(転換線、基準線、先行スパン1・2、遅行スパン)の計算方法と、Plotlyでの可視化方法を解説しました。Polarsのrolling_max()rolling_min()shift()を使うことで、一目均衡表の複雑な計算を簡潔に実装できます。特に、先行スパンと遅行スパンの時間軸のシフトをshift()で簡単に実現できるのが便利です。前回のボリンジャーバンド、移動平均線との関連性は低いですが、出来高などシンプルなサブチャートと組み合わせて一目均衡表のみで大まかなトレンドをつかめます。トレンドの変換直後の頭と尻尾を早めにつかむにはファンダメンタルズ分析が役立ちますが、一目均衡表だけでは掴みづらいです。ただ、一目均衡表だけで完結するわかりやすさと奥の深さが魅力的であるので、一目均衡表を学ぶ価値があると思います。

参考書籍:『Pythonで実践する株式投資分析』(片渕彼富 (著), 山田祥寛 (監修))

comment

コメントを読み込み中...