Plotly を使うと、価格・出来高・テクニカル指標を縦に並べた「触れるチャート」を Python だけで作れます。本記事は make_subplots で価格 + 出来高 + RSI + MACD のサブプロット構成を組み立てる手順を、コードと落とし穴つきで公開します。

目次

  1. 完成イメージ
  2. 必要なライブラリ
  3. サンプルデータ
  4. コード(コピペで動く)
  5. make_subplots のポイント
  6. 操作性を上げる小さな工夫
  7. 落とし穴

完成イメージ

内容
1 段目ローソク足 + SMA(25 / 75)
2 段目出来高(棒)
3 段目RSI(14) と 30 / 70 のラインを点線で重ねる
4 段目MACD ラインとシグナルライン、ヒストグラム

X 軸は 4 段すべてで共通。ズーム・パンも 4 段同期します。

必要なライブラリ

Terminal window
pip install pandas numpy plotly

検証バージョン: Python 3.12.5 / pandas 2.2.3 / plotly 5.20

サンプルデータ

prices.csv に次の列があるとします。

Date,O,H,L,C,Vo
2025-01-06,2840,2865,2825,2858,3200000
2025-01-07,2858,2890,2845,2872,2900000
...

コード(コピペで動く)

"""plotly_indicators.py
価格・出来高・RSI・MACD の 4 段サブプロットを作る。
"""
from __future__ import annotations
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
PRICES_PATH = "prices.csv"
def add_sma(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df["sma25"] = df["C"].rolling(25).mean()
df["sma75"] = df["C"].rolling(75).mean()
return df
def add_rsi(df: pd.DataFrame, window: int = 14) -> pd.DataFrame:
df = df.copy()
diff = df["C"].diff()
gain = diff.clip(lower=0)
loss = -diff.clip(upper=0)
avg_gain = gain.rolling(window).mean()
avg_loss = loss.rolling(window).mean()
rs = avg_gain / avg_loss
df["rsi"] = 100 - 100 / (1 + rs)
return df
def add_macd(df: pd.DataFrame, fast: int = 12, slow: int = 26, signal: int = 9) -> pd.DataFrame:
df = df.copy()
ema_fast = df["C"].ewm(span=fast, adjust=False).mean()
ema_slow = df["C"].ewm(span=slow, adjust=False).mean()
df["macd"] = ema_fast - ema_slow
df["macd_signal"] = df["macd"].ewm(span=signal, adjust=False).mean()
df["macd_hist"] = df["macd"] - df["macd_signal"]
return df
def build_chart(df: pd.DataFrame) -> go.Figure:
fig = make_subplots(
rows=4, cols=1,
shared_xaxes=True, # 4 段で X 軸を共通にしてズームを同期
row_heights=[0.45, 0.15, 0.20, 0.20],
vertical_spacing=0.02,
subplot_titles=("Price", "Vo", "RSI(14)", "MACD(12,26,9)"),
)
# 1 段目: ローソク足 + SMA
fig.add_trace(go.Candlestick(
x=df["Date"], open=df["O"], high=df["H"],
low=df["L"], close=df["C"], name="OHLC", showlegend=False,
), row=1, col=1)
fig.add_trace(go.Scatter(x=df["Date"], y=df["sma25"], name="SMA25",
line=dict(width=1)), row=1, col=1)
fig.add_trace(go.Scatter(x=df["Date"], y=df["sma75"], name="SMA75",
line=dict(width=1)), row=1, col=1)
# 2 段目: 出来高
fig.add_trace(go.Bar(x=df["Date"], y=df["Vo"], name="Vo",
marker=dict(line=dict(width=0)), showlegend=False),
row=2, col=1)
# 3 段目: RSI
fig.add_trace(go.Scatter(x=df["Date"], y=df["rsi"], name="RSI",
line=dict(width=1)), row=3, col=1)
for level in (30, 70):
fig.add_hline(y=level, line_dash="dot", line_width=1, row=3, col=1)
# 4 段目: MACD
fig.add_trace(go.Bar(x=df["Date"], y=df["macd_hist"], name="MACD hist",
showlegend=False, marker=dict(line=dict(width=0))),
row=4, col=1)
fig.add_trace(go.Scatter(x=df["Date"], y=df["macd"], name="MACD",
line=dict(width=1)), row=4, col=1)
fig.add_trace(go.Scatter(x=df["Date"], y=df["macd_signal"], name="Signal",
line=dict(width=1, dash="dash")), row=4, col=1)
fig.update_layout(
height=900, width=1100,
xaxis_rangeslider_visible=False, # ローソク足のレンジスライダーを消す
margin=dict(l=40, r=20, t=40, b=20),
)
fig.update_yaxes(title_text="Price", row=1, col=1)
fig.update_yaxes(title_text="Vo", row=2, col=1)
fig.update_yaxes(title_text="RSI", range=[0, 100], row=3, col=1)
fig.update_yaxes(title_text="MACD", row=4, col=1)
return fig
def main() -> None:
df = pd.read_csv(PRICES_PATH, parse_dates=["Date"]).sort_values("Date")
df = add_sma(df)
df = add_rsi(df)
df = add_macd(df)
fig = build_chart(df)
fig.write_html("chart.html")
# JupyterLab / Notebook なら fig.show()
if __name__ == "__main__":
main()
価格・出来高・RSI・MACD の 4 段サブプロット例

chart.html をブラウザで開くと、4 段同期の対話チャートが表示されます。マウスホイールで縦軸ズーム、ドラッグで横移動、ダブルクリックでリセットです。

make_subplots のポイント

パラメータ意味
rows, colsサブプロットの段数・列数
shared_xaxesTrue にすると X 軸が同期する(時系列の必須設定)
row_heights段の高さの比率
vertical_spacing段の間の隙間。0.02 〜 0.05 が読みやすい
subplot_titles段ごとの小見出し

add_trace 呼び出しで row=col= を指定して、どの段に置くかを決めます。

操作性を上げる小さな工夫

  • rangeslider_visible=False: ローソク足下のミニチャートを消すとスッキリする
  • hovermode="x unified": 同じ日付の 4 段をホバーで横断的に表示
  • Range selector: 1m / 3m / 1y のショートカットボタンを X 軸に追加可能(fig.update_xaxes(rangeselector=...))
  • テーマ: template="plotly_white" で白背景。資料化するときに見やすい
fig.update_layout(template="plotly_white", hovermode="x unified")

落とし穴

  • ファイルサイズ: 大量データ(数年 × 数銘柄)を 1 つの HTML にまとめると、ファイルが数十 MB に膨らみます。1 銘柄 1 ファイル が運用しやすい
  • マルチインデックス: 複数銘柄を一気に渡すには、長表(ロング形式)+ ループでサブプロット生成が向く
  • 欠損日: 営業日に穴があるとローソク足が変な間隔で並ぶ。fig.update_xaxes(rangebreaks=...) で土日祝を非表示にできる
  • 対数スケール: 長期チャートは fig.update_yaxes(type="log", row=1, col=1) で対数表示にすると変動率の比較がしやすい
  • MACD のヒストグラム色分け: 上昇・下落で色を変えたい場合は marker_color=np.where(df["macd_hist"] >= 0, "green", "red") を使う(macd_hist は終値から計算した派生列)

生成AI へのプロンプト例

「Plotly でテクニカル指標を重ねたチャートを描きたい」場合のプロンプト例です。

目的:
日次株価データを使って、Plotly でテクニカル指標を重ねたチャートを作る。
入力:
Date, O, H, L, C, Vo の 6 列を持つ pandas DataFrame
要件:
- make_subplots で 4 段(rows=4)
1. ローソク足 + SMA(25, 75)
2. 出来高(棒グラフ)
3. RSI(14) + 30/70 の点線ライン
4. MACD(12,26,9) ラインとシグナル + ヒストグラム
- 4 段で X 軸を同期(shared_xaxes=True)
- ローソク足のレンジスライダーは非表示
- ホバーは x unified
- 出力は HTML(fig.write_html("chart.html"))
制約:
- pandas 2.2 系 / plotly 5.20 系
- 関数を build_chart(df) として切り出す
- 計算ロジック(RSI/MACD/SMA)は別関数に分離

まとめ

  • make_subplots + shared_xaxes=True で価格・出来高・RSI・MACD の 4 段同期チャートを作れる
  • ローソク足のレンジスライダーは無効化、ホバーは x unified で読みやすくなる
  • 大量データを 1 ファイルにまとめずに済むよう、銘柄ごとに HTML を分ける
  • 日付の欠損は rangebreaks で対処、長期は対数スケールが効く