在前一篇測試中已對 Plotly 的 K 線圖類別 plotly.graph_objects.CandleStick 的用法有了基本了解, 本篇旨在利用 Figure 物件的 add_trace() 方法在畫布上添加子圖 (例如成交量或 RSI 等技術指標), 同時透過 plotly.subplots.make_subplot() 函式來規劃畫布上的子圖佈局.
4. 添加成交量子圖 :
要在畫布上繪製子圖有兩種方式, 第一種是使用較低階的絕對領域 (Domain) 劃分法, 此方法須手動切割 Y 軸空間自行布局, 好處是可以客製化布局, 缺點是維護難度較高, 調整彈性較差, 在 "最強 AI 投資分析" 這本書的第四章就是採用此方法, 參考 :
第二種方式是使用 plotly.subplots.make_subplot() 函式, 這是採用網格系統來進行排版的高階 API, 只要指定子圖的列 (row) 與欄 (col), Plotly 會在底層自動計算所有圖表的高度, 對齊與間距, 是一種較簡易的低耦合設計.
首先匯入子圖布局函式 make_subplots() :
from plotly.subplots import make_subplots
make_subplots() 的參數說明如下表 :
| 參數名稱 | 說明 |
|---|---|
| rows | 子圖的列數(預設為 1) |
| cols | 子圖的欄數(預設為 1) |
| shared_xaxes | 是否共用 X 軸(True / False / 'all' / 'rows' / 'columns') |
| shared_yaxes | 是否共用 Y 軸(True / False / 'all' / 'rows' / 'columns') |
| start_cell | 子圖起始位置('top-left' 或 'bottom-left') |
| subplot_titles | 每個子圖的標題(list) |
| specs | 自訂每個子圖的型態與配置(例如 type='xy', 'domain', 'scene' 等) |
| row_heights | 各列高度比例(list,例如 [0.7, 0.3]) |
| column_widths | 各欄寬度比例(list,例如 [0.6, 0.4]) |
| horizontal_spacing | 子圖之間的水平間距(0~1) |
| vertical_spacing | 子圖之間的垂直間距(0~1) |
| insets | 設定內嵌子圖(inset charts)的位置與大小 |
| column_titles | 每一欄的標題(list) |
| row_titles | 每一列的標題(list) |
| x_title | 整體 X 軸標題 |
| y_title | 整體 Y 軸標題 |
| figure | 將子圖加入既有的 Figure 物件 |
其中最常用的參數是 rows, cols, shared_xaxes, vertical_spacing, 與 row_heights 等.
下列程式將畫布做 2 列 1 欄布局, 第一列高度占 70% 放 K 線圖 trace; 第二列高度占 30% 放成交量長條圖 (有做漲紅跌綠顏色設定) :
# plotly_candlestick_5.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd
from plotly.subplots import make_subplots
if __name__ == "__main__":
df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
df.columns=df.columns.map(lambda x: x[0])
# === 建立子圖 (2 rows: 上列放 K 線圖,下列放成交量) ===
fig=make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3] # 列高度占比
)
# === K 線圖 ===
price=go.Candlestick(
x=df.index,
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['Close'],
increasing_line_color='red',
decreasing_line_color='green',
name='Price'
)
fig.add_trace(price, row=1, col=1) # K 線圖放 row 1
# === 成交量顏色(漲紅跌綠) ===
colors=['red' if c >= o else 'green' for c, o in zip(df['Close'], df['Open'])]
volume=go.Bar(
x=df.index,
y=df['Volume'],
marker_color=colors,
name='Volume'
)
fig.add_trace(volume, row=2, col=1) # 成交量放 row 2
# === 移除非交易日 ===
date_range=pd.date_range(start=df.index.min(), end=df.index.max())
breaks=date_range[~date_range.isin(df.index)]
breaks_list=breaks.tolist()
fig.update_xaxes(
rangebreaks=[{'values': breaks_list}],
showspikes=True,
spikethickness=1,
spikecolor='blue',
spikedash='dot',
spikemode='across'
)
# === Y 軸 spike(兩個子圖都套用)===
fig.update_yaxes(
showspikes=True,
spikethickness=1,
spikecolor='blue',
spikedash='dot',
spikemode='across'
)
# === Layout ===
fig.update_layout(
title='台灣五十 (0050) K線 + 成交量',
width=800,
height=700,
xaxis_rangeslider_visible=False,
hovermode='x unified' # 很重要:跨子圖同步 hover
)
fig.show()
注意, 之前沒有子圖時是呼叫 fig=go.Figure() 建立 Figure 物件, 但此處是用 make_subplots() 來建立. 其次, 有子圖的情況時, hovermode 參數務必要設為 'x unified', 否則會有多個 tooltip 分散, 以及子圖之間不同步問題 (無子圖時因所有 trace 都在同一個座標系故不會有這些問題).
結果如下 :
5. 添加技術指標子圖 :
接下來要在成交量下面添加 MACD 指標, 使用 pandas-ta 套件計算, 參考 :
這樣畫布上總共有三個子圖 : K 線圖, 成交量, 與 MACD.
程式碼如下 :
# plotly_candlestick_6.py
import yfinance as yf
import plotly.graph_objects as go
import pandas as pd
import pandas_ta as ta
from plotly.subplots import make_subplots
if __name__ == "__main__":
# === 下載資料 ===
df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
df.columns=df.columns.map(lambda x: x[0])
# === 計算 MACD(pandas-ta)===
macd=ta.macd(df['Close'], fast=12, slow=26, signal=9)
# pandas-ta 會回傳三欄 :contentReference[oaicite:0]{index=0}
df['MACD']=macd['MACD_12_26_9']
df['MACDs']=macd['MACDs_12_26_9'] # signal
df['MACDh']=macd['MACDh_12_26_9'] # histogram
# === 建立子圖 ===
fig=make_subplots(
rows=3,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02,
row_heights=[0.5, 0.2, 0.3]
)
# =====================
# 1. K線圖
# =====================
fig.add_trace(
go.Candlestick(
x=df.index,
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['Close'],
increasing_line_color='red',
decreasing_line_color='green',
name='Price'
),
row=1, col=1
)
# =====================
# 2. 成交量
# =====================
vol_colors=['red' if c >= o else 'green'
for c, o in zip(df['Close'], df['Open'])]
fig.add_trace(
go.Bar(
x=df.index,
y=df['Volume'],
marker_color=vol_colors,
name='Volume'
),
row=2, col=1
)
# =====================
# 3. MACD
# =====================
# Histogram(紅綠柱)
macd_colors=['red' if v >= 0 else 'green' for v in df['MACDh']]
fig.add_trace(
go.Bar(
x=df.index,
y=df['MACDh'],
marker_color=macd_colors,
name='MACD Hist'
),
row=3, col=1
)
# MACD 線
fig.add_trace(
go.Scatter(
x=df.index,
y=df['MACD'],
line=dict(color='blue'),
name='MACD'
),
row=3, col=1
)
# Signal 線
fig.add_trace(
go.Scatter(
x=df.index,
y=df['MACDs'],
line=dict(color='orange'),
name='Signal'
),
row=3, col=1
)
# =====================
# 移除非交易日
# =====================
date_range=pd.date_range(start=df.index.min(), end=df.index.max())
breaks=date_range[~date_range.isin(df.index)]
fig.update_xaxes(
rangebreaks=[{'values': breaks.tolist()}],
showspikes=True,
spikethickness=1,
spikecolor='blue',
spikedash='dot',
spikemode='across'
)
fig.update_yaxes(
showspikes=True,
spikethickness=1,
spikecolor='blue',
spikedash='dot',
spikemode='across'
)
# =====================
# Layout
# =====================
fig.update_layout(
title='0050 台灣五十 - K線 + 成交量 + MACD',
width=900,
height=800,
xaxis_rangeslider_visible=False,
hovermode='x unified'
)
fig.show()
注意, MACD 的紅綠柱, MACD 線與信號線都是要畫在 row=3 子圖疊在一起, 結果如下 :



沒有留言 :
張貼留言