2026年5月3日 星期日

Python 學習筆記 : 用 bokeh 繪製 K 線圖 (二)

Bokeh 的優點是高自由度與高效能, 但其 API 較低階, 故並未內建像 Plotly 的 CandleStick 那樣的類別, 本篇旨在為 Bokey 山寨一個 CandleStick 來簡化 K 線圖的繪製. 為了讓程式碼不至於過度複雜, 以下測試不處理非交易日 K 棒空缺問題. 

本系列全部測試文章索引參考 :



4. 用封裝的類別繪製 K 線圖 :   

下面範例改寫自前一篇測試的 bokeh_candlestick_3.py, 將繪製 K 線圖的邏輯封裝在自訂的 BokehChart 類別裡, 只要呼叫其建構式並傳入 title/width/height 參數建立物件, 然後在主程式中呼叫 add_candlestick(df) 即可繪製 K 線圖, 程式碼如下 : 

# bokeh_candlestick_class_1.py
import yfinance as yf
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span

class BokehChart:
    """封裝 Bokeh K線圖與技術分析工具的繪圖類別"""
    def __init__(self, title="K線圖", width=800, height=400):
        # 建立並初始化畫布
        self.fig=figure(
            x_axis_type='datetime', 
            title=title, 
            width=width,
            height=height,
            tools='xpan, xwheel_zoom, box_zoom, reset, save'
            )
        self.fig.grid.grid_line_alpha=0.3
        
        # 初始化虛線十字游標
        self._add_crosshair()

    def _add_crosshair(self):
        """內部方法:加入虛線十字游標"""
        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)
        self.fig.add_tools(CrosshairTool(overlay=[w_span, h_span]))

    def add_candlestick(self, df, date_col='Date', open_col='Open', high_col='High', low_col='Low', close_col='Close', vol_col='Volume'):
        """對外介面:加入 K 線圖與懸停工具"""
        # 複製資料以避免修改到原始 DataFrame
        data=df.copy()
        
        # 定義漲跌顏色 (紅漲綠跌)
        data['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(data[open_col], data[close_col])]
        
        # 建立資料來源
        source=ColumnDataSource(data)
        
        # 繪製上下影線
        self.fig.segment(date_col, high_col, date_col, low_col, color="black", source=source)
        
        # 繪製 K 棒 (12 小時寬度)
        width_ms=12 * 60 * 60 * 1000
        self.fig.vbar(date_col, width_ms, open_col, close_col, 
                      fill_color='color', line_color='black', source=source)
        
        # 加入互動式懸停工具 (HoverTool)
        hover=HoverTool(
            tooltips=[
                ("日期", f"@{date_col}{{%F}}"),
                ("開盤", f"@{open_col}{{0.00}}"),
                ("收盤", f"@{close_col}{{0.00}}"),
                ("最高", f"@{high_col}{{0.00}}"),
                ("最低", f"@{low_col}{{0.00}}"),
                ("成交量", f"@{vol_col}{{0,0}}")
                ],
            formatters={f'@{date_col}': 'datetime'}
            )
        self.fig.add_tools(hover)

    def show(self):
        """顯示圖表"""
        show(self.fig)

# ==========================================
# 主程式:使用類別繪圖 (如同 Plotly 般簡潔)
# ==========================================
if __name__ == "__main__":
    # 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. 實例化畫布並繪圖
    chart=BokehChart(title='0050.TW 台灣50 ETF K線圖 (2026-03 ~ 2026-04)', width=800, height=400)
    chart.add_candlestick(df)
    
    # 3. 顯示圖表
    chart.show()

結果如下 :




5. 用封裝的類別繪製 K 線圖 + 成交量副圖 :   

如果要同時繪製 K 線圖與成交量副圖, 則須匯入 bokeh.layouts.column() 函式進行垂直布局, 讓主圖 (fig_k) 與副圖能共用 X 軸並連動. 程式碼改寫如下 : 

# bokeh_candlestick_class_2.py
import yfinance as yf
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span, CustomJSTickFormatter
from bokeh.layouts import column  # 引入垂直佈局工具

class BokehChart:
    """封裝 Bokeh K線圖與成交量子圖的繪圖類別"""
    
    # 稍微調整初始化參數,允許分別設定主圖與副圖的高度
    def __init__(self, title="K線圖", width=800, height_k=400, height_v=150):
        # 1. 建立主圖 (K線圖)
        self.fig_k=figure(
            title=title, 
            width=width, height=height_k,
            tools='xpan, xwheel_zoom, box_zoom, reset, save'
            )
        self.fig_k.grid.grid_line_alpha=0.3
        self.fig_k.xaxis.visible=False  # 隱藏主圖的 X 軸,讓畫面更緊湊
        
        # 2. 建立副圖 (成交量圖)
        self.fig_v=figure(
            width=width, height=height_v,
            x_range=self.fig_k.x_range,  # 【關鍵連動】將副圖的 X 軸範圍綁定為主圖的 X 軸
            tools='xpan, xwheel_zoom, reset'
            )
        self.fig_v.grid.grid_line_alpha=0.3
        self.fig_v.y_range.start=0     # 確保成交量 Y 軸從 0 開始
        
        # 3. 組合為垂直佈局
        self.layout=column(self.fig_k, self.fig_v)
        
        # 4. 初始化虛線十字游標 (加在兩張圖上)
        self._add_crosshair(self.fig_k)
        self._add_crosshair(self.fig_v)

    def _add_crosshair(self, fig):
        """內部方法:為指定的畫布加入虛線十字游標"""
        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]))

    def add_candlestick(self, df, date_col='Date', open_col='Open', high_col='High', low_col='Low', close_col='Close', vol_col='Volume'):
        """對外介面:處理資料並繪製 K 線與成交量"""
        data=df.copy()
        
        # 建立連續整數序列 (消除假日空缺)
        data['seq']=range(len(data))
        
        # 定義漲跌顏色
        data['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(data[open_col], data[close_col])]
        
        # 用 Python 預先處理好 Bokeh 風格的日期字串
        date_map={}
        prev_year=None
        for i, d in enumerate(data[date_col]):
            label=f"{d.strftime('%b')} {d.day}"
            if i == 0 or d.year != prev_year:
                label += f"\n{d.year}"
            date_map[str(i)]=label
            prev_year=d.year
            
        source=ColumnDataSource(data)
        width_val=0.8  # 因為 X 軸變成了連續整數,寬度直接用比例 0.8
        
        # --- 繪製主圖 (K線) ---
        self.fig_k.segment('seq', high_col, 'seq', low_col, color="black", source=source)
        self.fig_k.vbar('seq', width_val, open_col, close_col, fill_color='color', line_color='black', source=source)
        
        # --- 繪製副圖 (成交量) ---
        # 使用 vbar,top 參數指定為成交量欄位
        self.fig_v.vbar('seq', width_val, top=vol_col, fill_color='color', line_color='black', source=source)
        
        # --- 設定副圖的 X 軸日期標籤 ---
        js_code="""
            var idx=Math.round(tick).toString();
            if (date_map[idx] !== undefined) {
                return date_map[idx];
            } else {
                return "";
            }
        """
        self.fig_v.xaxis.formatter=CustomJSTickFormatter(code=js_code, args={'date_map': date_map})
        self.fig_v.xaxis.major_label_orientation=0 
        self.fig_v.xaxis.major_label_standoff=15
        
        # --- 加入互動式懸停工具 ---
        hover=HoverTool(
            tooltips=[
                ("日期", f"@{date_col}{{%F}}"),
                ("開盤", f"@{open_col}{{0.00}}"),
                ("收盤", f"@{close_col}{{0.00}}"),
                ("最高", f"@{high_col}{{0.00}}"),
                ("最低", f"@{low_col}{{0.00}}"),
                ("成交量", f"@{vol_col}{{0,0}}")
                ],
            formatters={f'@{date_col}': 'datetime'},
            mode='vline'  # 【推薦】改為 vline 模式,滑鼠對齊同一個垂直線就會觸發
            )
        self.fig_k.add_tools(hover)
        self.fig_v.add_tools(hover)

    def show(self):
        """顯示圖表佈局"""
        show(self.layout)  # 【修改】現在是顯示整個 layout 而不是單一 fig

# ==========================================
# 主程式
# ==========================================
if __name__ == "__main__":
    # 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. 建立畫布並繪圖
    chart=BokehChart(title='0050.TW 台灣50 ETF (含同步成交量)')
    chart.add_candlestick(df)
    
    # 3. 顯示圖表
    chart.show()

注意, 此例之 BokehChart 類別還封裝了處理非交易日空缺的邏輯, 這讓整個畫布上的 K 棒看起來是連續無空缺的, 結果如下 : 




6. 用封裝的類別繪製 K 線圖 + 成交量 + 技術指標副圖 : 

上面範例中的 BokehChart 類別把主圖與副圖邏輯都寫在類別裡, 每次要添加技術指標副圖都要去改類別並不符合軟工原則, 若要讓程式碼具有擴充性, 必須對架構做一次重構, 要建立一個 動態串列 self.figures = [] 的來儲存所有畫布, 並增加通用副圖方法, 例如 add_bar_subplot() (畫成交量, MACD柱狀圖) 與 add_line_subplot() (畫 RSI, 均線).

程式碼如下 : 

# bokeh_candlestick_class_3.py
import yfinance as yf
import pandas as pd
import pandas_ta as ta  # 
from bokeh.plotting import figure
from bokeh.io import show as show_bokeh # 避免名稱衝突
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span, CustomJSTickFormatter
from bokeh.layouts import column

class BokehChart:
    """高擴充性 Bokeh 繪圖類別,支援動態疊加無限多個副圖"""
    
    def __init__(self, title="K線圖", width=800, main_height=400):
        self.width=width
        self.title=title
        self.main_height=main_height
        
        # 動態儲存所有畫布的清單 (主圖會在 index 0)
        self.figures=[]
        self.source=None
        self.date_map={}
        self.fig_k=None # 參照主圖,用來同步 x_range

    def _add_crosshair(self, fig):
        """內部方法:加入虛線十字游標"""
        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]))

    def add_candlestick(self, df, date_col='Date', open_col='Open', high_col='High', low_col='Low', close_col='Close'):
        """【步驟 1】:初始化主圖與共用資料源"""
        data=df.copy()
        
        # 建立連續整數序列 (消除假日空缺)
        data['seq']=range(len(data))
        data['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(data[open_col], data[close_col])]
        
        # 製作日期對照表
        self.date_map={}
        prev_year=None
        for i, d in enumerate(data[date_col]):
            label=f"{d.strftime('%b')} {d.day}"
            if i == 0 or d.year != prev_year:
                label += f"\n{d.year}"
            self.date_map[str(i)]=label
            prev_year=d.year
            
        # 建立全局共用的資料源 (此時 df 裡面已經包含外部算好的 RSI 等指標)
        self.source=ColumnDataSource(data)
        
        # 建立主畫布
        self.fig_k=figure(title=self.title, width=self.width, height=self.main_height, tools='xpan, xwheel_zoom, box_zoom, reset, save')
        self.fig_k.grid.grid_line_alpha=0.3
        
        # 繪製 K 線
        self.fig_k.segment('seq', high_col, 'seq', low_col, color="black", source=self.source)
        self.fig_k.vbar('seq', 0.8, open_col, close_col, fill_color='color', line_color='black', source=self.source)
        
        # 設定主圖 Hover
        hover=HoverTool(
            tooltips=[("日期", f"@{date_col}{{%F}}"), ("開盤", f"@{open_col}{{0.00}}"), ("收盤", f"@{close_col}{{0.00}}")],
            formatters={f'@{date_col}': 'datetime'}, mode='vline'
            )
        self.fig_k.add_tools(hover)
        self._add_crosshair(self.fig_k)
        
        # 加入畫布清單
        self.figures.append(self.fig_k)

    def add_bar_subplot(self, col_name, title="", color_col=None, color='blue', height=150):
        """通用介面:新增柱狀圖副圖 (例如:成交量、MACD柱)"""
        fig=figure(width=self.width, height=height, x_range=self.fig_k.x_range, tools='xpan, xwheel_zoom, reset')
        fig.grid.grid_line_alpha=0.3
        fig.y_range.start=0
        
        # 若有指定顏色欄位 (如成交量的紅綠色),則使用該欄位;否則使用單一顏色
        fill_c=color_col if color_col else color
        fig.vbar('seq', 0.8, top=col_name, fill_color=fill_c, line_color='black', source=self.source)
        
        # 專屬 Hover
        hover=HoverTool(tooltips=[("日期", "@Date{%F}"), (title or col_name, f"@{col_name}{{0,0}}")], formatters={'@Date': 'datetime'}, mode='vline')
        fig.add_tools(hover)
        self._add_crosshair(fig)
        self.figures.append(fig)

    def add_line_subplot(self, col_name, title="", color='blue', height=150, hlines=None):
        """通用介面:新增折線圖副圖 (例如:RSI、均線、KD)"""
        fig=figure(width=self.width, height=height, x_range=self.fig_k.x_range, tools='xpan, xwheel_zoom, reset')
        fig.grid.grid_line_alpha=0.3
        
        fig.line('seq', col_name, color=color, line_width=1.5, source=self.source)
        
        # 支援加入水平參考線 (例如 RSI 的 30, 70 超買超賣線)
        if hlines:
            for y_val in hlines:
                line=Span(location=y_val, dimension='width', line_color='gray', line_dash='dashed', line_alpha=0.5)
                fig.add_layout(line)

        # 專屬 Hover
        hover=HoverTool(tooltips=[("日期", "@Date{%F}"), (title or col_name, f"@{col_name}{{0.00}}")], formatters={'@Date': 'datetime'}, mode='vline')
        fig.add_tools(hover)
        self._add_crosshair(fig)
        self.figures.append(fig)

    def show(self):
        """渲染圖表:隱藏除最後一張圖外的 X 軸,並套用自訂日期標籤"""
        for i, fig in enumerate(self.figures):
            # 不是最後一張圖,隱藏 X 軸
            if i < len(self.figures) - 1:
                fig.xaxis.visible=False
            # 是最後一張圖,掛上 JavaScript 日期查表邏輯
            else:
                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': self.date_map})
                fig.xaxis.major_label_orientation=0
                fig.xaxis.major_label_standoff=15

        # 垂直疊加所有被加入的圖表
        layout=column(*self.figures)
        show_bokeh(layout)


# ==========================================
# 主程式:展現架構擴充性的時刻
# ==========================================
if __name__ == "__main__":
    # 1. 獲取資料
    df=yf.download('0050.tw', start='2025-10-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    df=df.reset_index()

    # 2. 在外部計算所有技術指標 (類別完全不需要知道 RSI 怎麼算的)
    df['RSI']=ta.rsi(df['Close'], length=14)

    # 3. 開始繪圖 (如堆積木般直覺)
    chart=BokehChart(title='0050.TW 台灣50 ETF (整合 RSI 指標)')
    
    # 初始化主圖與資料源
    chart.add_candlestick(df)
    
    # 呼叫通用介面新增成交量 (使用 color 欄位決定紅綠)
    chart.add_bar_subplot('Volume', title='成交量', color_col='color', height=100)
    
    # 呼叫通用介面新增 RSI (自訂顏色,並加上 30/70 水平參考線)
    chart.add_line_subplot('RSI', title='RSI(14)', color='purple', height=150, hlines=[30, 70])
    
    # 顯示成果
    chart.show()

此例使用 pandas_ta 套件計算 RSI 指標值, 結果放進 df['RSI'] 欄位, 結果如下 : 




7. 獨立的 Bokeh K 線圖模組 : 

我們可以將上面範例的 BokehChart 類別寫在一個獨立的模組檔案例如 bokeh_chart.py 中, 再用 import 方式匯入 BokehChart 類別來繪製 K 線圖, 這樣主程式檔就會較簡潔了. 

模組檔案 bokeh_chart.py 內容如下 :

# bokeh_chart.py
import pandas as pd
from bokeh.plotting import figure
from bokeh.io import show as show_bokeh
from bokeh.models import ColumnDataSource, HoverTool, CrosshairTool, Span, CustomJSTickFormatter
from bokeh.layouts import column

class BokehChart:
    """高擴充性 Bokeh 繪圖類別,支援動態疊加無限多個副圖"""
    
    def __init__(self, title='K線圖', width=800, main_height=400):
        self.width=width
        self.title=title
        self.main_height=main_height
        self.figures=[]
        self.source=None
        self.date_map={}
        self.fig_k=None 

    def _add_crosshair(self, fig):
        """加入虛線十字游標"""
        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]))

    def add_candlestick(self, df, date_col='Date', open_col='Open', high_col='High', low_col='Low', close_col='Close'):
        """初始化主圖與共用資料源"""
        data=df.copy()
        # 建立連續整數序列與漲跌顏色
        data['seq']=range(len(data))
        data['color']=['#ff0000' if c >= o else '#00aa00' for o, c in zip(data[open_col], data[close_col])]
        # 製作日期對照表
        self.date_map={}
        prev_year=None
        for i, d in enumerate(data[date_col]):
            label=f"{d.strftime('%b')} {d.day}"
            if i == 0 or d.year != prev_year:
                label += f"\n{d.year}"
            self.date_map[str(i)]=label
            prev_year=d.year
        self.source=ColumnDataSource(data)
        self.fig_k=figure(title=self.title, width=self.width, height=self.main_height, tools='xpan, xwheel_zoom, box_zoom, reset, save')
        self.fig_k.grid.grid_line_alpha=0.3
        self.fig_k.segment('seq', high_col, 'seq', low_col, color="black", source=self.source)
        self.fig_k.vbar('seq', 0.8, open_col, close_col, fill_color='color', line_color='black', source=self.source)
        hover=HoverTool(
            tooltips=[("日期", f"@{date_col}{{%F}}"), ("開盤", f"@{open_col}{{0.00}}"), ("收盤", f"@{close_col}{{0.00}}")],
            formatters={f'@{date_col}': 'datetime'}, mode='vline'
            )
        self.fig_k.add_tools(hover)
        self._add_crosshair(self.fig_k)
        self.figures.append(self.fig_k)

    def add_bar_subplot(self, col_name, title="", color_col=None, color='blue', height=150, on_fig=None, y_min_zero=False):
        """新增柱狀圖副圖 (支援傳入畫布疊加到現有畫布)"""
        # 如果有指定 on_fig,就用現成的畫布;否則建立新畫布
        if on_fig:
            fig=on_fig
        else:
            fig=figure(width=self.width, height=height, x_range=self.fig_k.x_range, tools='xpan, xwheel_zoom, reset')
            fig.grid.grid_line_alpha=0.3
            self.figures.append(fig) # 只有新畫布才需要加入清單
            self._add_crosshair(fig) # 只有新畫布才需要加十字線
        # 只有成交量這類數值絕對為正的圖表,才需要強制 Y 軸從 0 開始
        if y_min_zero:
            fig.y_range.start=0
        fill_c=color_col if color_col else color
        fig.vbar('seq', 0.8, top=col_name, fill_color=fill_c, line_color='black', source=self.source)
        # 專屬 Hover
        hover=HoverTool(tooltips=[("日期", "@Date{%F}"), (title or col_name, f"@{col_name}{{0.00}}")], formatters={'@Date': 'datetime'}, mode='vline')
        fig.add_tools(hover)
        return fig # 回傳畫布讓後續的線可以畫在它上面

    def add_line_subplot(self, col_name, title="", color='blue', height=150, hlines=None, on_fig=None):
        """新增折線圖副圖 (支援疊加到現有畫布)"""
        # 如果有指定 on_fig,就用現成的畫布;否則建立新畫布
        if on_fig:
            fig = on_fig
        else:
            fig = figure(width=self.width, height=height, x_range=self.fig_k.x_range, tools='xpan, xwheel_zoom, reset')
            fig.grid.grid_line_alpha = 0.3
            self.figures.append(fig)
            self._add_crosshair(fig)
        fig.line('seq', col_name, color=color, line_width=1.5, source=self.source)
        if hlines:
            for y_val in hlines:
                line = Span(location=y_val, dimension='width', line_color='gray', line_dash='dashed', line_alpha=0.5)
                fig.add_layout(line)
        hover = HoverTool(tooltips=[("日期", "@Date{%F}"), (title or col_name, f"@{col_name}{{0.00}}")], formatters={'@Date': 'datetime'}, mode='vline')
        fig.add_tools(hover)
        return fig # 回傳畫布讓後續的線可以畫在它上面

    def show(self):
        """渲染圖表"""
        for i, fig in enumerate(self.figures):
            if i < len(self.figures) - 1:
                fig.xaxis.visible=False
            else:
                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': self.date_map})
                fig.xaxis.major_label_orientation=0
                fig.xaxis.major_label_standoff=15
        layout=column(*self.figures)
        show_bokeh(layout)

欲繪製 K 線圖+成交量+技術指標時, 主程式檔只要先從 bokeh_chart.py 匯入 BokehChart 類別並建立 BokehChart 物件後, 呼叫 add_candlestick() 繪製主圖 (K 線圖), 呼叫 add_bar_subplot() 或 add_line_subplot() 添加副圖 (成交量 & 技術指標), 下面範例添加了 RSI 與 MACD 副圖 :

# bokeh_candlestick_class_4.py
import yfinance as yf
import pandas as pd
import pandas_ta as ta
from bokeh_chart import BokehChart

if __name__ == "__main__":
    # 1. 獲取資料
    df=yf.download('0050.tw', start='2025-10-01', end='2026-04-30', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    df=df.reset_index()

    # 2. 計算技術指標
    # RSI
    df['RSI']=ta.rsi(df['Close'], length=14)
    # MACD (將計算結果合併回原本的 df)
    macd_df=ta.macd(df['Close'])
    df=pd.concat([df, macd_df], axis=1)
    # 計算 MACD 柱狀圖的顏色 (大於等於 0 紅色,小於 0 綠色)
    df['macd_color']=['#ff0000' if m >= 0 else '#00aa00' for m in df['MACDh_12_26_9']]

    # 3. 畫圖
    # 畫主圖 : K 線圖
    chart=BokehChart(title='0050.TW 台灣50 ETF (整合 MACD)')
    chart.add_candlestick(df)  
    # 畫成交量副圖 (加上 y_min_zero=True,避免長條圖懸空)
    chart.add_bar_subplot('Volume', title='成交量', color_col='color', height=100, y_min_zero=True)
    # ==========================================
    # 繪製 MACD 複合副圖
    # ==========================================
    # 第一步:先畫 MACD 柱狀圖,並用變數 macd_fig 把畫布接住
    macd_fig=chart.add_bar_subplot('MACDh_12_26_9', title='MACD柱', color_col='macd_color', height=150)
    # 第二步:把 MACD 線畫在 macd_fig 身上
    chart.add_line_subplot('MACD_12_26_9', title='MACD線', color='blue', on_fig=macd_fig)
    # 第三步:把 Signal 線也畫在 macd_fig 身上
    chart.add_line_subplot('MACDs_12_26_9', title='Signal線', color='orange', on_fig=macd_fig)
    # ==========================================

    chart.show()

注意, 此處 MACD 指標因為含有快線, 慢線, 與柱狀圖三個圖形, 通常都是疊在一個畫布上, 所以要分成三個步驟把三個圖層疊上去, 首先繪製柱狀圖, 然後將傳回的畫布物件 macd_fig 在後續繪製快線與慢線時傳給 on_fonfig 參數, 這樣三張圖就會疊在同一張畫布上了. 

結果如下 : 




可見將繪圖類別獨立出去後, 主程式結構就清爽易讀多了. 


沒有留言 :