Plotly を使うと、価格・出来高・テクニカル指標を縦に並べた「触れるチャート」を Python だけで作れます。本記事は make_subplots で価格 + 出来高 + RSI + MACD のサブプロット構成を組み立てる手順を、コードと落とし穴つきで公開します。
目次
- 完成イメージ
- 必要なライブラリ
- サンプルデータ
- コード(コピペで動く)
- make_subplots のポイント
- 操作性を上げる小さな工夫
- 落とし穴
完成イメージ
| 段 | 内容 |
|---|---|
| 1 段目 | ローソク足 + SMA(25 / 75) |
| 2 段目 | 出来高(棒) |
| 3 段目 | RSI(14) と 30 / 70 のラインを点線で重ねる |
| 4 段目 | MACD ラインとシグナルライン、ヒストグラム |
X 軸は 4 段すべてで共通。ズーム・パンも 4 段同期します。
必要なライブラリ
pip install pandas numpy plotly検証バージョン: Python 3.12.5 / pandas 2.2.3 / plotly 5.20
サンプルデータ
prices.csv に次の列があるとします。
Date,O,H,L,C,Vo2025-01-06,2840,2865,2825,2858,32000002025-01-07,2858,2890,2845,2872,2900000...コード(コピペで動く)
"""plotly_indicators.py価格・出来高・RSI・MACD の 4 段サブプロットを作る。"""from __future__ import annotations
import numpy as npimport pandas as pdimport plotly.graph_objects as gofrom 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()
chart.html をブラウザで開くと、4 段同期の対話チャートが表示されます。マウスホイールで縦軸ズーム、ドラッグで横移動、ダブルクリックでリセットです。
make_subplots のポイント
| パラメータ | 意味 |
|---|---|
rows, cols | サブプロットの段数・列数 |
shared_xaxes | True にすると 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 で対処、長期は対数スケールが効く