出来高プロファイル(Volume Profile)は、時間軸ではなく 価格帯ごとに出来高を積み上げて 表示する分析手法です。本記事では、定義の整理・pandas での簡易実装・チャートとの並列表示までを扱います。

目次

  1. 出来高プロファイルが示すもの
  2. 計算の考え方
  3. サンプルデータの準備
  4. 価格ビンを作る
  5. POC と Value Area を計算する
  6. チャートと並べて描く
  7. 高安均等分割版

出来高プロファイルが示すもの

通常のチャートは「横軸=時間、縦軸=価格」で、出来高は下段に時系列で並びます。出来高プロファイルは縦軸を価格、横軸を出来高に取り直した 横棒グラフ です。

得られる情報は次のとおりです。

  • POC(Point of Control): もっとも出来高が多かった価格帯。「市場が長く滞在した価格」
  • VAH / VAL(Value Area High / Low): 全出来高の 70% を含む上下の境界
  • High Volume Node / Low Volume Node: 出来高が多い帯 / 少ない帯。サポート・レジスタンス候補

「価格帯にどれだけ売買が積み上がっているか」を直感的に見ることが目的です。時系列だけ見ていると気づきにくい「滞在時間の偏り」を可視化できます。

計算の考え方

最も簡単な方式は、当日の終値が属する価格ビンに、当日の出来高をすべて足す やり方です。

VP(b)=t:PtbVt\mathrm{VP}(b) = \sum_{t : P_t \in b} V_t

ここで bb は価格ビン、VtV_t は当日の出来高です。

より精緻に分解するなら、当日の高値・安値の幅を均等に分けて出来高を配分する方法もあります(TPO 方式 / 高安均等分割)。本記事は終値ベースの簡易版で進めます。

サンプルデータの準備

import numpy as np
import pandas as pd
rng = np.random.default_rng(seed=99)
n = 200
returns = rng.normal(loc=0.0003, scale=0.013, size=n)
close = 1500 * np.exp(np.cumsum(returns))
high = close * (1 + np.abs(rng.normal(scale=0.008, size=n)))
low = close * (1 - np.abs(rng.normal(scale=0.008, size=n)))
volume = rng.integers(80_000, 600_000, size=n)
ohlc = pd.DataFrame(
{"H": high, "L": low, "C": close, "Vo": volume},
index=pd.date_range("2025-08-01", periods=n, freq="B"),
)

価格ビンを作る

pd.cut で終値を等間隔のビンに割り振ります。

bin_count = 30
price_min = ohlc["L"].min()
price_max = ohlc["H"].max()
edges = np.linspace(price_min, price_max, bin_count + 1)
labels = (edges[:-1] + edges[1:]) / 2 # 各ビンの中心価格
ohlc["bin"] = pd.cut(
ohlc["C"],
bins=edges,
labels=labels,
include_lowest=True,
)
profile = (
ohlc.groupby("bin", observed=True)["Vo"]
.sum()
.sort_index()
)
print(profile.head(10))

observed=True は、Categorical を使ったときに「実データに現れたカテゴリだけ」を集計するためのオプションです。

POC と Value Area を計算する

poc_price = profile.idxmax()
poc_volume = profile.max()
# 全体出来高の 70% を含む上下境界 = Value Area
total = profile.sum()
target = total * 0.70
# POC を中心に、両隣のビンを大きい方から取り込んでいく
sorted_by_vol = profile.sort_values(ascending=False)
acc = 0.0
included = []
for price, vol in sorted_by_vol.items():
included.append(price)
acc += vol
if acc >= target:
break
val = min(included)
vah = max(included)
print(f"POC: {poc_price:.1f} ({poc_volume:,.0f})")
print(f"VAL: {val:.1f}")
print(f"VAH: {vah:.1f}")

「Value Area を POC から両隣に伸ばしていく」アルゴリズムは、より厳密には「左右で出来高が大きい側を 1 ビンずつ取り込む」反復が原典です。簡易版として、出来高上位から 70% に達するまで取り込む方式でも実用的な範囲です。

チャートと並べて描く

価格チャートを左、プロファイルを右に置く 2 列レイアウトです。

import matplotlib.pyplot as plt
fig, (ax_price, ax_vp) = plt.subplots(
ncols=2, sharey=True, figsize=(12, 6),
gridspec_kw={"width_ratios": [3, 1]},
)
ax_price.plot(ohlc.index, ohlc["C"], color="black", linewidth=0.8)
ax_price.set_title("C")
ax_price.set_xlabel("Date")
ax_price.set_ylabel("Price")
ax_price.grid(alpha=0.3)
bin_height = (price_max - price_min) / bin_count * 0.9
ax_vp.barh(
profile.index.astype(float), profile.values,
height=bin_height, color="tab:blue", alpha=0.6,
)
ax_vp.axhline(poc_price, color="red", linewidth=1.0, label="POC")
ax_vp.axhline(vah, color="orange", linewidth=0.8, linestyle="--", label="VAH")
ax_vp.axhline(val, color="orange", linewidth=0.8, linestyle="--", label="VAL")
ax_vp.set_title("Vo Profile")
ax_vp.set_xlabel("Vo")
ax_vp.legend()
ax_vp.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("volume_profile.png", dpi=120)
plt.close(fig)
価格チャートと出来高プロファイルを左右に並べた複合チャート

sharey=True で価格軸を揃えるのがコツです。プロファイル側は barh で横棒にします。

高安均等分割版

精度を上げる場合、各日の高値〜安値の範囲に出来高を均等分配する方式に切り替えます。

def volume_profile_hl(df: pd.DataFrame, bins: int = 30) -> pd.Series:
edges = np.linspace(df["L"].min(), df["H"].max(), bins + 1)
centers = (edges[:-1] + edges[1:]) / 2
profile = pd.Series(0.0, index=centers)
for _, row in df.iterrows():
lo, hi, vol = row["L"], row["H"], row["Vo"]
# その日が触れた価格ビンに均等分配
mask = (centers >= lo) & (centers <= hi)
if mask.sum() == 0:
continue
profile[centers[mask]] += vol / mask.sum()
return profile
profile_hl = volume_profile_hl(ohlc, bins=30)

ビンを跨がない日が出ないよう、ビン数 (bins) は値幅の広さに応じて調整します。終値版と比べると、よりなだらかなプロファイルになります。

注意点

  • 約定価格の精度: 簡易版は終値・高安だけを使うため、本来はティックデータ単位で計算する出来高プロファイルとは差が出る
  • 期間の選び方: 半年・1 年など期間が長いと滞在価格が偏り、POC が「過去の中心」になる。短い期間で何度も再計算するほうが現状を映す
  • イベントの影響: 株式分割・大型決算・配当落ちの前後では価格水準が不連続になり、ビン分割が歪む。調整済み終値を使う
  • シグナル化は慎重に: 「POC = サポート」のような単純な解釈は、強いトレンドが出ているときには通用しない

生成AI へのプロンプト例

ロング形式の DataFrame に対し、Code ごとに最終 N 日のプロファイルを返す関数を依頼します。

入力 DataFrame:
- columns: Date, Code, H, L, C, Vo
Code ごとに、最新の lookback 日(デフォルト 120)に対する
高安均等分配方式の出来高プロファイルを計算し、
Code, bin_center, Vo の 3 列の DataFrame を返す
関数 volume_profile(df, lookback=120, bins=30) を書いてください。
要件:
- pandas 2.2 系
- 各 Code 内で日付昇順に整列
- bins は Code ごとに [L.min(), H.max()] を等間隔に分割
- 末尾に簡単な動作確認サンプルを付ける

まとめ

  • 出来高プロファイルは「価格帯 × 出来高」の横棒グラフ。滞在時間の偏り が見える
  • 簡易版は pd.cut + groupby で実装できる
  • POC・VAH・VAL は出来高プロファイルの代表的な指標
  • 高安均等分配にすれば終値版より滑らかになる
  • ティック非対応の簡易版である点・期間設定の影響に注意して使う