本系列全部測試文章索引參考 :
首先匯入繪圖用的函式 figure() 與 show() :
>>> from bokeh.plotting import figure, show
然後從元件模組 bokeh.models 中匯入匯入用來繪製 K 棒的 ColumnDataSource 類別, 以及繪製互動效果的懸停工具類別 HoverTool :
>>> from bokeh.models import ColumnDataSource, HoverTool
接著匯入 Pandas 來處理 OHLC 資料 :
>>> import pandas as pd
>>> data={
'date': pd.to_datetime(['2026-05-01', '2026-05-02', '2026-05-03', '2026-05-04']),
'open': [100, 110, 105, 120],
'high': [115, 120, 110, 130],
'low': [95, 105, 100, 115],
'close': [110, 105, 108, 125]
}
>>> df=pd.DataFrame(data)
在 df 中添加一個 color 欄位來記錄漲跌顏色 (紅漲綠跌) :
>>> df['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(df.open, df.close)]
呼叫 ColumnDataSource 類別的建構式 ColumnDataSource() 並傳入 df 建立 ColumnDataSource 物件 :
>>> source=ColumnDataSource(df)
>>> type(source)
<class 'bokeh.models.sources.ColumnDataSource'>
呼叫 figure() 函式建立畫布物件 :
>>> fig=figure(
x_axis_type='datetime',
title='Bokeh K線圖',
width=800,
height=400
)
呼叫 segment() 函式繪製上下影線 :
>>> fig.segment('date', 'high', 'date', 'low', color='black', source=source)
GlyphRenderer(id='p1138', ...)
呼叫 Figure 物件的 vbar() 方法繪製 K 棒 :
>>> fig.vbar('date', pd.Timedelta(hours=12), 'open', 'close',
fill_color='color', line_color='black', source=source)
GlyphRenderer(id='p1147', ...)
呼叫 HoverTool 類別的建構式建立懸停工具物件 :
>>> hover=HoverTool(
tooltips=[
("日期", "@date{%F}"),
("開盤", "@open"),
("收盤", "@close"),
("最高", "@high"),
("最低", "@low")
],
formatters={'@date': 'datetime'}
)
呼叫 Figure 物件的 add_tool() 方法將懸停工具物件加入畫布中 :
>>> fig.add_tools(hover)
這樣便可呼叫 show() 函式來顯示畫布了 :
>>> show(fig)
Bokeh 會自動開啟瀏覽器顯示網頁來呈現畫布內容 :
完整程式碼如下 :
# bokeh_candlestick_1.py
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool
# OHLC 資料 :
data={
'date': pd.to_datetime(['2026-05-01', '2026-05-02', '2026-05-03', '2026-05-04']),
'open': [100, 110, 105, 120],
'high': [115, 120, 110, 130],
'low': [95, 105, 100, 115],
'close': [110, 105, 108, 125]
}
df=pd.DataFrame(data)
# 定義漲跌顏色
df['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(df.open, df.close)]
source=ColumnDataSource(df)
# 建立畫布
fig=figure(
x_axis_type='datetime',
title='Bokeh K線圖',
width=800,
height=400,
#tools='pan, wheel_zoom, box_zoom, reset, save'
)
# 繪製上下影線 (Segment 物件)
fig.segment('date', 'high', 'date', 'low', color="black", source=source)
# 繪製 K 棒 (Vbar 物件)
# width 設定為 12 小時 (以毫秒計算) 以確保條形之間有間隔
fig.vbar('date', pd.Timedelta(hours=12), 'open', 'close',
fill_color='color', line_color='black', source=source)
# 加入互動式懸停工具 (HoverTool)
hover=HoverTool(
tooltips=[
("日期", "@date{%F}"),
("開盤", "@open"),
("收盤", "@close"),
("最高", "@high"),
("最低", "@low")
],
formatters={'@date': 'datetime'}
)
fig.add_tools(hover)
# 輸出與顯示
#output_file("candlestick.html")
show(fig)
1. 繪製從 yfinance 下載的實盤 K 線圖 :
下面範例改從 yfinance 抓取實盤 OHLC 資料來繪製 K 線圖, 程式碼如下 :
# bokeh_candlestick_2.py
import yfinance as yf
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool
# 1. 下載真實 OHLCV 資料
df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
# 攤平 yfinance 最新版本回傳的 MultiIndex 欄位
df.columns=df.columns.map(lambda x: x[0])
# yfinance 的日期會被設為索引, 重設為一般欄位 以利 ColumnDataSource 讀取
df=df.reset_index()
# 2. 定義漲跌顏色 (紅漲綠跌)
df['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(df['Open'], df['Close'])]
# 3. 建立資料來源
source=ColumnDataSource(df)
# 4. 建立畫布
fig=figure(
x_axis_type='datetime',
title='0050.TW 台灣50 ETF K線圖 (2026-03 ~ 2026-04)',
width=800,
height=400,
# 加入十字游標 (crosshair) 與鎖定 X 軸縮放(xwheel_zoom) 以利看盤
tools='xpan, xwheel_zoom, box_zoom, crosshair, reset, save'
)
# 讓網格線淡一點,視覺更聚焦在 K 棒上
fig.grid.grid_line_alpha=0.3
# 5. 繪製上下影線
fig.segment('Date', 'High', 'Date', 'Low', color="black", source=source)
# 6. 繪製 K 棒
# Bokeh 處理時間軸時寬度用毫秒計算最為精準與穩定。12小時=12 * 60 * 60 * 1000 毫秒
width_ms=12 * 60 * 60 * 1000
fig.vbar('Date', width_ms, 'Open', 'Close',
fill_color='color', line_color='black', source=source)
# 7. 加入互動式懸停工具 (HoverTool)
# 加上了數值格式化 {0.00} 讓小數點對齊並順手加入成交量 (Volume)
hover=HoverTool(
tooltips=[
("日期", "@Date{%F}"),
("開盤", "@Open{0.00}"),
("收盤", "@Close{0.00}"),
("最高", "@High{0.00}"),
("最低", "@Low{0.00}"),
("成交量", "@Volume{0,0}")
],
formatters={'@Date': 'datetime'}
)
fig.add_tools(hover)
# 8. 輸出與顯示
show(fig)
此例的關鍵之處是 yf 下載的 df 日期欄位會被設為索引, 所以必須呼叫 df.reset_index() 將日期欄位重設為一般欄位, 這樣 ColumnDataSource 物件才能讀得到, 另外, 懸停工具則添加了 yf 取得的成交量資訊, 結果如下 :
可見十字線會隨著滑鼠在畫布中移動, 當指向 K 棒時會出現懸停提示, 顯示 OHLCV 數據.
2. 客製化的虛線十字輔助線 :
預設的十字線是實線, 如果要改為虛線須取消 tools 參數中的 crosshair, 然後匯入 CrosshairTool, Span 這兩個類別 :
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span
其中 CrosshairTool 用來建立客製化十字輔助線, 而 Span 則用來建立具有 line_dash 屬性的 Span (跨距線) 物件, 然後把這些線覆蓋 (overlay) 到 CrosshairTool 物件上, 程式改寫如下 :
# bokeh_candlestick_3.py
import yfinance as yf
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span
# 1. 下載真實 OHLCV 資料
df=yf.download('0050.tw', start='2026-03-01', end='2026-04-30', auto_adjust=True)
# 攤平 yfinance 最新版本回傳的 MultiIndex 欄位
df.columns=df.columns.map(lambda x: x[0])
# yfinance 的日期會被設為索引, 重設為一般欄位 以利 ColumnDataSource 讀取
df=df.reset_index()
# 2. 定義漲跌顏色 (紅漲綠跌)
df['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(df['Open'], df['Close'])]
# 3. 建立資料來源
source=ColumnDataSource(df)
# 4. 建立畫布
fig=figure(
x_axis_type='datetime',
title='0050.TW 台灣50 ETF K線圖 (2026-03 ~ 2026-04)',
width=800,
height=400,
# 鎖定 X 軸縮放(xwheel_zoom) 以利看盤 (取消 crosshair)
tools='xpan, xwheel_zoom, box_zoom, , reset, save'
)
# 讓網格線淡一點,視覺更聚焦在 K 棒上
fig.grid.grid_line_alpha=0.3
# 5. 繪製上下影線
fig.segment('Date', 'High', 'Date', 'Low', color="black", source=source)
# 6. 繪製 K 棒
# Bokeh 處理時間軸時寬度用毫秒計算最為精準與穩定。12小時=12 * 60 * 60 * 1000 毫秒
width_ms=12 * 60 * 60 * 1000
fig.vbar('Date', width_ms, 'Open', 'Close',
fill_color='color', line_color='black', source=source)
# 7. 加入互動式懸停工具 (HoverTool)
# 加上了數值格式化 {0.00} 讓小數點對齊並順手加入成交量 (Volume)
hover=HoverTool(
tooltips=[
("日期", "@Date{%F}"),
("開盤", "@Open{0.00}"),
("收盤", "@Close{0.00}"),
("最高", "@High{0.00}"),
("最低", "@Low{0.00}"),
("成交量", "@Volume{0,0}")
],
formatters={'@Date': 'datetime'}
)
fig.add_tools(hover)
# 8. 加入虛線十字游標 (建立水平與垂直方向的 Span 物件, 設定為 dashed 虛線)
w_span=Span(dimension="width", line_dash="dashed", line_color="gray", line_alpha=0.6, line_width=1)
h_span=Span(dimension="height", line_dash="dashed", line_color="gray", line_alpha=0.6, line_width=1)
fig.add_tools(CrosshairTool(overlay=[w_span, h_span]))
# 9. 輸出與顯示
show(fig)
結果如下 :
可見十字輔助線已經變成虛線了.
3. 消除非交日空缺 :
與 Plotly 一樣, Bokeh 在繪製 K 線圖時會自動把 yfinance 下載的 OHLCV 資料 X 軸的日期序列補齊, 使得 K 棒在非交易日無 K 棒出現不連續的空缺. Plotly 的 Figure 物件有內建的 rangebreaks 參數可用來隱藏這些空缺, 但 Bokeh 並沒有.
在 Bokeh 中可以用 Index Mapping 來解決此問題. 概念是不要把 X 軸當成時間, 而是把它當成 連續的整數數列 (0, 1, 2, 3...), 然後透過一個標籤轉換器騙過使用者的眼睛, 讓 X 軸的數字顯示成對應的日期. 具體而言是透過 CustomJSTickFormatter 來實現, 但如果要維持原本 Bokeh 的 X 軸標籤顯示風格, 必須修改 Javascript 程式碼, 例如 :
# bokeh_candlestick_4.py
import yfinance as yf
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span, CustomJSTickFormatter
# 1. 下載真實 OHLCV 資料
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])
df=df.reset_index()
# 2. 建立連續整數序列
df['seq']=range(len(df))
# 用 Python 預先將日期轉為 Bokeh 風格的 "Apr 1" 格式
date_map={}
prev_year=None
for i, d in enumerate(df['Date']):
# 取得月份縮寫與日期 (例如 "Apr 1")
# strftime('%b') 會輸出英文月份縮寫,d.day 會輸出不補零的日期
label=f"{d.strftime('%b')} {d.day}"
# 如果是圖表的第一根 K 棒,或者是跨年的第一根 K 棒,在底下加上年份
# 使用 \n 來讓 Bokeh 產生換行效果
if i == 0 or d.year != prev_year:
label += f"\n{d.year}"
date_map[str(i)]=label
prev_year=d.year
# 3. 定義漲跌顏色
df['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(df['Open'], df['Close'])]
# 4. 建立資料來源
source=ColumnDataSource(df)
# 5. 建立畫布
fig=figure(
title='0050.TW 台灣50 ETF K線圖 (完美日期標籤版)',
width=800,
height=400,
tools='xpan, xwheel_zoom, box_zoom, reset, save'
)
fig.grid.grid_line_alpha=0.3
# 6. 使用 JavaScript 取出排版好的字串
js_code="""
var idx=Math.round(tick).toString();
if (date_map[idx] !== undefined) {
return date_map[idx];
} else {
return "";
}
"""
fig.xaxis.formatter=CustomJSTickFormatter(code=js_code, args={'date_map': date_map})
# 把傾斜拿掉 (設為 0) 讓標籤恢復水平,這樣帶有 \n 年份的文字才會對齊
fig.xaxis.major_label_orientation=0
# 拉開標籤與 X 軸的間距 (預設通常是 5)
fig.xaxis.major_label_standoff=15
# 7. 繪製上下影線
fig.segment('seq', 'High', 'seq', 'Low', color="black", source=source)
# 8. 繪製 K 棒
width_val=0.8
fig.vbar('seq', width_val, 'Open', 'Close',
fill_color='color', line_color='black', source=source)
# 9. 加入互動式懸停工具
hover=HoverTool(
tooltips=[
("日期", "@Date{%F}"),
("開盤", "@Open{0.00}"),
("收盤", "@Close{0.00}"),
("最高", "@High{0.00}"),
("最低", "@Low{0.00}"),
("成交量", "@Volume{0,0}")
],
formatters={'@Date': 'datetime'}
)
fig.add_tools(hover)
# 10. 加入虛線十字游標
w_span=Span(dimension="width", line_dash="dashed", line_color="gray", line_alpha=0.6, line_width=1)
h_span=Span(dimension="height", line_dash="dashed", line_color="gray", line_alpha=0.6, line_width=1)
fig.add_tools(CrosshairTool(overlay=[w_span, h_span]))
# 11. 輸出與顯示
show(fig)
結果如下 :
不過這樣的程式碼看起來就有點複雜了.




沒有留言 :
張貼留言