Pythonで平均足を計算してダマシを回避するコード実装

Python実装・コード

※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。

ローソク足チャートを見ていて、トレンドの方向が頻繁に入れ替わり、売買の判断に迷った経験があるはずです。特に短期足では「ダマシ」と呼ばれるノイズが多く、エントリーとイグジットのタイミングを掴みにくい場面が頻発します。

平均足(Heikin-Ashi)は、通常のローソク足の四本値を独自の計算式で加工し、トレンドの方向性をより明確に可視化するチャート手法です。陽線・陰線の連続性が高まるため、トレンドの継続と転換を直感的に判断できます。

📘 外部参考Heikin-Ashi 解説(Investopedia)

しかし、平均足の計算ロジックを正しく理解しないまま使っている投資家は少なくありません。「なんとなく見やすい」という感覚的な利用にとどまり、売買ルールへの組み込みができていないケースがほとんどです。

原因は、平均足の始値が「1本前の平均足の始値と終値の平均」という再帰的な構造を持つ点にあります。この再帰計算をスプレッドシートで正確に実装するのは手間がかかり、ミスも生じやすいです。

本記事では、pandasを使って平均足の四本値を正確に算出し、トレンド判定と売買シグナルの生成までを自動化するPythonコードを提供します。さらに、通常のローソク足との比較チャートを出力する応用コードも解説します。

コードはすべてコピペで動作します。SBI証券で取引する銘柄の分析に、そのまま組み込んでください。

平均足の計算ロジックと通常ローソク足との違い

平均足の四本値の計算式

平均足(Heikin-Ashi)は、通常のローソク足の四本値(始値・高値・安値・終値)を以下の計算式で変換したものです。

項目 計算式
HA終値(HA Close) (始値 + 高値 + 安値 + 終値) ÷ 4
HA始値(HA Open) (1本前のHA始値 + 1本前のHA終値) ÷ 2
HA高値(HA High) 高値・HA始値・HA終値の最大値
HA安値(HA Low) 安値・HA始値・HA終値の最小値

HA始値の計算には「1本前の平均足」が必要です。この再帰的な構造が平均足の核心であり、実装上の注意点でもあります。最初の1本は通常ローソク足の始値をそのまま使用してください。

通常ローソク足に対する優位性と限界

平均足の最大の利点は、ノイズの除去です。通常のローソク足では陽線と陰線が頻繁に入れ替わる局面でも、平均足では同色の連続が保たれやすくなります。

ただし、平均足は加工データであることを忘れてはいけません。実際の始値・終値とは異なるため、平均足の値で指値注文を出すのは危険です。あくまでトレンド方向の判定ツールとして使い、発注価格は通常の四本値を参照してください。

【コピペOK】平均足の算出とトレンド判定のメインコード

まず、必要なライブラリをインストールしてください。


pip install yfinance pandas matplotlib mplfinance japanize-matplotlib

以下がメインコードです。


import datetime
from typing import Optional

import pandas as pd
import yfinance as yf
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import mplfinance as mpf
import japanize_matplotlib  # noqa: F401 – 日本語フォント有効化

# ==============================
# 設定エリア
# ==============================
TICKER: str = "7203.T"            # 分析対象の銘柄コード(例:トヨタ自動車)
START_DATE: str = "2024-01-01"    # データ取得開始日
END_DATE: str = "2025-12-31"      # データ取得終了日
OUTPUT_HA_CHART: str = "heikin_ashi_chart.png"
OUTPUT_SIGNAL: str = "heikin_ashi_signals.csv"


# ==============================
# データ取得
# ==============================
def fetch_ohlc_data(
    ticker: str,
    start: str,
    end: str,
) -> pd.DataFrame:
    '銘柄のOHLCデータを取得して返す'
    df: pd.DataFrame = yf.download(
        ticker,
        start=start,
        end=end,
        auto_adjust=True,
        progress=False,
    )
    if df.empty:
        raise ValueError(f"{ticker} のデータ取得に失敗しました。ティッカーを確認してください。")
    # MultiIndex対策:カラムをフラット化
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)
    return df[["Open", "High", "Low", "Close", "Volume"]].copy()


# ==============================
# 平均足の算出
# ==============================
def calc_heikin_ashi(ohlc: pd.DataFrame) -> pd.DataFrame:
    '通常OHLCから平均足の四本値を算出する'
    ha: pd.DataFrame = pd.DataFrame(index=ohlc.index)

    # HA Close = (O + H + L + C) / 4
    ha["HA_Close"] = (
        ohlc["Open"] + ohlc["High"] + ohlc["Low"] + ohlc["Close"]
    ) / 4.0

    # HA Open(再帰計算)
    ha["HA_Open"] = 0.0
    ha.iloc[0, ha.columns.get_loc("HA_Open")] = ohlc["Open"].iloc[0]
    for i in range(1, len(ha)):
        ha.iloc[i, ha.columns.get_loc("HA_Open")] = (
            ha.iloc[i - 1, ha.columns.get_loc("HA_Open")]
            + ha.iloc[i - 1, ha.columns.get_loc("HA_Close")]
        ) / 2.0

    # HA High / HA Low
    ha["HA_High"] = pd.concat(
        [ohlc["High"], ha["HA_Open"], ha["HA_Close"]], axis=1,
    ).max(axis=1)
    ha["HA_Low"] = pd.concat(
        [ohlc["Low"], ha["HA_Open"], ha["HA_Close"]], axis=1,
    ).min(axis=1)

    # mplfinance互換カラムを追加
    ha["Open"] = ha["HA_Open"]
    ha["High"] = ha["HA_High"]
    ha["Low"] = ha["HA_Low"]
    ha["Close"] = ha["HA_Close"]
    ha["Volume"] = ohlc["Volume"]

    return ha


# ==============================
# トレンド判定
# ==============================
def add_trend_signal(ha: pd.DataFrame) -> pd.DataFrame:
    '平均足の陽線・陰線からトレンドシグナルを付与する'
    ha = ha.copy()
    ha["color"] = "bullish"
    ha.loc[ha["HA_Close"] < ha["HA_Open"], "color"] = "bearish"

    # 連続陽線・連続陰線のカウント
    ha["streak"] = 0
    streak: int = 0
    prev_color: str = '
    for i in range(len(ha)):
        current_color: str = ha.iloc[i, ha.columns.get_loc("color")]
        if current_color == prev_color:
            streak += 1
        else:
            streak = 1
        ha.iloc[i, ha.columns.get_loc("streak")] = streak
        prev_color = current_color

    # 売買シグナル:色の転換をエントリーポイントとする
    ha["signal"] = "hold"
    for i in range(1, len(ha)):
        prev: str = ha.iloc[i - 1, ha.columns.get_loc("color")]
        curr: str = ha.iloc[i, ha.columns.get_loc("color")]
        if prev == "bearish" and curr == "bullish":
            ha.iloc[i, ha.columns.get_loc("signal")] = "buy"
        elif prev == "bullish" and curr == "bearish":
            ha.iloc[i, ha.columns.get_loc("signal")] = "sell"

    return ha


# ==============================
# 可視化:平均足チャート
# ==============================
def plot_heikin_ashi(
    ha: pd.DataFrame,
    ticker: str,
    filepath: str,
) -> None:
    '平均足チャートをPNG画像として保存する'
    ha_plot: pd.DataFrame = ha[["Open", "High", "Low", "Close", "Volume"]].copy()
    ha_plot.index = pd.DatetimeIndex(ha_plot.index)

    mc = mpf.make_marketcolors(up="red", down="blue", edge="inherit", volume="in")
    style = mpf.make_mpf_style(marketcolors=mc)

    fig, axes = mpf.plot(
        ha_plot,
        type="candle",
        style=style,
        volume=True,
        title=f"{ticker} 平均足チャート",
        figsize=(12, 6),
        returnfig=True,
    )
    fig.savefig(filepath, dpi=150, bbox_inches="tight")
    plt.close(fig)
    print(f"平均足チャートを保存しました: {filepath}")


# ==============================
# シグナル一覧のCSV出力
# ==============================
def export_signals(ha: pd.DataFrame, filepath: str) -> None:
    '売買シグナルが発生した日のみCSVに出力する'
    signals: pd.DataFrame = ha[ha["signal"] != "hold"][
        ["HA_Open", "HA_Close", "color", "streak", "signal"]
    ].copy()
    signals.to_csv(filepath)
    print(f"シグナル一覧を保存しました: {filepath}({len(signals)}件)")


# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
    # 1. データ取得
    ohlc: pd.DataFrame = fetch_ohlc_data(TICKER, START_DATE, END_DATE)

    # 2. 平均足算出
    ha: pd.DataFrame = calc_heikin_ashi(ohlc)

    # 3. トレンド判定・シグナル付与
    ha = add_trend_signal(ha)

    # 4. サマリー表示
    buy_count: int = len(ha[ha["signal"] == "buy"])
    sell_count: int = len(ha[ha["signal"] == "sell"])
    max_bullish: int = int(ha.loc[ha["color"] == "bullish", "streak"].max())
    max_bearish: int = int(ha.loc[ha["color"] == "bearish", "streak"].max())
    print("=" * 40)
    print(f"銘柄: {TICKER}")
    print(f"期間: {START_DATE} ~ {END_DATE}")
    print(f"買いシグナル: {buy_count}回")
    print(f"売りシグナル: {sell_count}回")
    print(f"最長連続陽線: {max_bullish}本")
    print(f"最長連続陰線: {max_bearish}本")
    print("=" * 40)

    # 5. 可視化
    plot_heikin_ashi(ha, TICKER, OUTPUT_HA_CHART)

    # 6. シグナルCSV出力
    export_signals(ha, OUTPUT_SIGNAL)

コードの処理フロー解説

上記のコードは、以下の6ステップで構成されています。

* ステップ1 データ取得yf.download()で指定銘柄のOHLC(四本値)と出来高を取得する。MultiIndexが返される場合のフラット化処理も含む

* ステップ2 平均足算出calc_heikin_ashi()でHA Close→HA Open(再帰ループ)→HA High・HA Lowの順に計算する。HA Openの再帰計算が最も重要な処理である

* ステップ3 トレンド判定:HA Close > HA Openなら陽線(bullish)、逆なら陰線(bearish)と判定し、色の転換点を売買シグナルとして付与する

* ステップ4 サマリー表示:売買シグナルの発生回数と最長連続陽線・陰線の本数をコンソールに出力する

* ステップ5 可視化mplfinanceで平均足のローソク足チャートをPNG画像として保存する

* ステップ6 CSV出力:シグナル発生日の一覧をCSVファイルに書き出す

TICKERを変更すれば任意の銘柄に対応できます。日経平均(^N225)やETF(1321.T)でも動作します。

平均足シグナルの読み解き方とダマシ回避の実践知識

陽線・陰線の連続本数で信頼度を測る

平均足の色が転換した直後は「ダマシ」の可能性があります。転換後に同色が3本以上連続した場合に初めてトレンド確定と判断してください。

コードのstreakカラムがこの連続本数を記録しています。streak >= 3のフィルタを追加すれば、信頼度の高いシグナルだけに絞り込めます。

下ヒゲなし陽線・上ヒゲなし陰線の意味

平均足で下ヒゲのない陽線は、強い上昇トレンドの継続を示します。逆に、上ヒゲのない陰線は強い下降トレンドです。ヒゲの長さはトレンドの勢いの弱まりを意味するため、長いヒゲが出現したらトレンド転換の準備をしてください。

この判定はHA_Low == HA_Open(下ヒゲなし陽線)やHA_High == HA_Open(上ヒゲなし陰線)の条件式で自動化できます。

【コピペOK】通常ローソク足と平均足を並べて比較する応用コード


import pandas as pd
import yfinance as yf
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import mplfinance as mpf
import japanize_matplotlib  # noqa: F401

# ==============================
# 設定エリア
# ==============================
TICKER: str = "7203.T"
START_DATE: str = "2024-07-01"
END_DATE: str = "2024-12-31"
OUTPUT_COMPARE: str = "compare_candle_ha.png"


# ==============================
# 平均足算出(メインコードと同一ロジック)
# ==============================
def calc_heikin_ashi(ohlc: pd.DataFrame) -> pd.DataFrame:
    '通常OHLCから平均足の四本値を算出する'
    ha: pd.DataFrame = pd.DataFrame(index=ohlc.index)
    ha["Close"] = (ohlc["Open"] + ohlc["High"] + ohlc["Low"] + ohlc["Close"]) / 4.0
    ha["Open"] = 0.0
    ha.iloc[0, ha.columns.get_loc("Open")] = ohlc["Open"].iloc[0]
    for i in range(1, len(ha)):
        ha.iloc[i, ha.columns.get_loc("Open")] = (
            ha.iloc[i - 1, ha.columns.get_loc("Open")]
            + ha.iloc[i - 1, ha.columns.get_loc("Close")]
        ) / 2.0
    ha["High"] = pd.concat([ohlc["High"], ha["Open"], ha["Close"]], axis=1).max(axis=1)
    ha["Low"] = pd.concat([ohlc["Low"], ha["Open"], ha["Close"]], axis=1).min(axis=1)
    ha["Volume"] = ohlc["Volume"]
    return ha


# ==============================
# 比較チャートの描画
# ==============================
def plot_comparison(
    ohlc: pd.DataFrame,
    ha: pd.DataFrame,
    ticker: str,
    filepath: str,
) -> None:
    '通常足と平均足を上下に並べたチャートを保存する'
    mc = mpf.make_marketcolors(up="red", down="blue", edge="inherit", volume="in")
    style = mpf.make_mpf_style(marketcolors=mc)

    fig = plt.figure(figsize=(12, 9))

    ax1 = fig.add_subplot(2, 1, 1)
    ax1.set_title(f"{ticker} 通常ローソク足")
    mpf.plot(ohlc, type="candle", style=style, ax=ax1)

    ax2 = fig.add_subplot(2, 1, 2)
    ax2.set_title(f"{ticker} 平均足")
    mpf.plot(ha, type="candle", style=style, ax=ax2)

    fig.tight_layout()
    fig.savefig(filepath, dpi=150, bbox_inches="tight")
    plt.close(fig)
    print(f"比較チャートを保存しました: {filepath}")


# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
    df: pd.DataFrame = yf.download(
        TICKER, start=START_DATE, end=END_DATE, auto_adjust=True, progress=False,
    )
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)

    ohlc: pd.DataFrame = df[["Open", "High", "Low", "Close", "Volume"]].copy()
    ha: pd.DataFrame = calc_heikin_ashi(ohlc)
    plot_comparison(ohlc, ha, TICKER, OUTPUT_COMPARE)

コードの処理フロー解説

上記のコードは、以下の3ステップで構成されています。

* ステップ1 データ取得:半年分のOHLCデータを取得し、通常足と平均足の両方を準備する

* ステップ2 平均足算出:メインコードと同一のcalc_heikin_ashi()で変換する

* ステップ3 比較描画matplotlibのサブプロットで上段に通常足、下段に平均足を並べてPNG保存する

通常足と平均足を並べることで、平均足がいかにノイズを除去しているかを視覚的に実感できます。START_DATEEND_DATEの期間を短くすれば、細部の違いをより鮮明に確認できます。

よくあるエラーと対処法

KeyError: ‘Open’が発生する

yfinanceのバージョンによって、返却されるDataFrameがMultiIndex構造になる場合があります。この場合、df["Open"]でアクセスするとKeyErrorが発生します。

📘 外部参考yfinance 公式GitHubPyPI

以下を試してください。

* コード内のdf.columns = df.columns.get_level_values(0)が実行されているか確認する

* print(df.columns)でカラム構造を出力し、期待どおりのフラット構造か確認する

* yfinanceを最新版に更新する(pip install --upgrade yfinance

平均足チャートが表示されず白紙画像になる

mplfinanceがデータのインデックスをDatetimeIndexとして認識できていない場合に発生します。yfinanceから取得したデータのインデックス型が変わっていることが原因です。

以下を試してください。

* ha.index = pd.DatetimeIndex(ha.index)でインデックスを明示的に変換する

* print(ha.index.dtype)datetime64[ns]になっているか確認する

* データが数行しかない場合はチャート描画に失敗するため、最低30営業日以上のデータを用意する

平均足の初日の値が明らかにおかしい

HA Openの初期値の設定ミスが原因です。最初の1本は通常ローソク足の始値をそのまま使用する必要がありますが、0やNaNで初期化してしまうと、以降すべての計算が連鎖的に狂います。

対処法として、calc_heikin_ashi()内のha.iloc[0, ha.columns.get_loc("HA_Open")] = ohlc["Open"].iloc[0]の行が正しく実行されているか確認してください。この初期値の設定が再帰計算の起点となるため、省略や変更は厳禁です。

まとめ

この記事では、Pythonを使って平均足(Heikin-Ashi)を算出し、トレンド判定と売買シグナル生成を自動化する方法を解説しました。

要点を整理します。

* 平均足のHA Openは「1本前のHA始値とHA終値の平均」という再帰計算で求める。初期値には通常ローソク足の始値を使用する

* 平均足の色の転換を売買シグナルとし、同色3本以上の連続でトレンド確定と判断する

* 下ヒゲなし陽線は強い上昇、上ヒゲなし陰線は強い下降を意味する。ヒゲの出現はトレンド衰退のサインである

* 平均足は加工データであるため、発注価格には必ず通常の四本値を使用する

* 通常足と平均足の比較チャートを出力すれば、ノイズ除去の効果を視覚的に確認できる

次のステップとして、平均足のシグナルと移動平均線やRSIなどの他のテクニカル指標を組み合わせてください。例えば、平均足が陽転かつRSIが30以下からの上昇という複合条件にすれば、ダマシの発生率をさらに低減できます。add_trend_signal()関数に条件を追加するだけで実装可能です。

📘 外部参考RSI(Wikipedia 日本語)RSI(Investopedia)

📘 外部参考Moving Average(Investopedia)

📘 外部参考移動平均(Wikipedia 日本語)

🔗 関連記事

Pythonで移動平均線を表示する方法【pandas・matplotlib・コードあり】

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