2025年1月5日 星期日

Python 學習筆記 : 在 Gradio 與 Streamlit app 上繪製 K 線圖

Python 用來繪製 K 線圖的主要套件是 mplfinance, 它可根據傳入的 OHLCV 價量資料 DataFrame 繪製 K 線, 並提供了豐富的函式可繪製疊圖與副圖, 搭配 Ta-Lib 套件則可在副圖中繪製各種技術指標, 我也寫了一個 kbar.py 模組來簡化畫 K 線圖時的參數設定, 參考 :

但若要同時可在命令列以及 Gradio 與 Streamlit 的 web app 中利用此 kbar.py 繪製 K 線圖的話, 必須改寫之前的 kbar.py, 因為 mplfinance 的 plot() 函式預設傳回 None, 這在命令列執行沒有問題, 但若要在 Gradio 與 Streamlit 的 web app 中使用 mplfinance 畫 K 線圖, 則必須傳入 returnfig=True 參數, 這樣 plot() 函式就會傳回 (fig, axes) 元組, 只要將畫布物件 fig 傳給 Gradio 的 Plot() 函式或 Streamlit 的 pyplot() 函式即可. 修改後的 kbar.py 如下 : 

# kbar.py
import mplfinance as mpf

class KBar():
    def __init__(self, df):
        self.df=df
        self.addplots=[]
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)
        self.addplots.append(plot)
    def plot(self, embedding=False, **kwargs):
        color=mpf.make_marketcolors(up='red', down='green', inherit=True)   
        font={'font.family': 'Microsoft JhengHei'}   
        style=mpf.make_mpf_style(base_mpf_style='default',
                                 marketcolors=color,
                                 rc=font)
        kwargs['type']='candle'
        kwargs['style']=style
        kwargs['addplot']=self.addplots
        if embedding:
            fig, axes=mpf.plot(self.df, returnfig=True, **kwargs)
            return fig
        else:
            mpf.plot(self.df, **kwargs)

主要是在 KBar 類別的 plot() 方法中添加 embedding 參數, 預設值為 False, 因此在命令列呼叫 plot() 方法時不必傳入 embedding 參數; 但在 Gradio 或 Streamlit app 中則要傳入 embedding=True. 

以下測試使用 yfinance 套件取得台股的 OHLCV 價量資料, 用法參考 :



1. 在 Gradio app 中繪製 K 線圖 : 

Gradio 是 HuggingFace 所維護的開放原始碼 web app 套件, 關於 Gradio 用法參考 :


以下是在 Gradio 中使用 mplfinance 畫 K 線圖範例 :

# gradio_kbar_1.py 
import gradio as gr
import kbar  # 匯入 kbar
import yfinance as yf  # 匯入 Yahoo Finance 
from talib.abstract import RSI, MFI  # 匯入 Ta-Lib 的技術指標類別

def plot_kbar():
    df=yf.download('0050.tw', start='2024-07-01', end='2024-08-21')  # 取得價量資料
    df.columns=[column.lower() for column in df.columns]  # Ta-Lib 要求欄位名稱為全小寫
    kb=kbar.KBar(df)  # 建立 KBar 物件
    rsi=RSI(df)  # 建立 RSI 指標物件
    mfi=MFI(df, timeperiod=14)  # 建立 MFI 指標物件
    if not rsi.isna().all():  # 確保 RSI 有值
        kb.addplot(rsi, panel=2, ylabel='RSI')
    if not mfi.isna().all():  # 確保 MFI 有值
        kb.addplot(mfi, panel=3, ylabel='MFI')
    # 繪製 K 線圖與技術指標副圖    
    fig=kb.plot(volume=True, mav=[3, 5, 7], embedding=True, title='台灣五十')  
    return fig  # 傳回 Figure 物件

iface=gr.Interface(
    fn=plot_kbar,  # 繪製函數
    inputs=[],              # 無需用戶輸入
    outputs=gr.Plot(),      # 輸出為 Gradio 的 Plot 物件
    title='Gradio Candle Chart',
    flagging_mode='never',
    )

iface.launch()

此例在呼叫 KBar 物件的 plot() 方法時傳入 embedding=True 參數, 這樣就會傳回畫布物件 fig, 將其傳回給 Gradio.Plot() 即可在 Gradio app 中顯示 mplfinance 繪製的圖形, 結果如下 :




2. 在 Streamlit app 中繪製 K 線圖 :

Streamlit 是一個輕量好用的 Python web app 套件, 廣泛應用在機器學習, 資料科學, 與人工智慧專案的展示應用上. 下面是在 Streamlit app 中用 mplfinance 繪製 K 線圖範例 : 

# streamlit_kbar_1.py
import streamlit as st
import kbar  # 匯入 kbar
import yfinance as yf  # 匯入 Yahoo Finance
from talib.abstract import RSI, MFI  # 匯入 Ta-Lib 的技術指標類別

df=yf.download('0050.tw', start='2024-07-01', end='2024-08-21')  # 取得價量資料
df.columns=[column.lower() for column in df.columns]  # Ta-Lib 要求欄位名稱為全小寫
kb=kbar.KBar(df)  # 建立 KBar 物件
rsi=RSI(df)  # 建立 RSI 指標物件
mfi=MFI(df, timeperiod=14)  # 建立 MFI 指標物件
if not rsi.isna().all():  # 確保 RSI 有值
    kb.addplot(rsi, panel=2, ylabel='RSI')
if not mfi.isna().all():  # 確保 MFI 有值
    kb.addplot(mfi, panel=3, ylabel='MFI')
# 繪製顯示成交量與 3, 5, 7 日均線之 K 線圖
fig=kb.plot(volume=True, mav=[3, 5, 7], embedding=True)  
st.title("Streamlit Candle Chart")
st.pyplot(fig)

此例在呼叫 KBar 物件的 plot() 方法時傳入 embedding=True 參數, 這樣就會傳回畫布物件 fig, 將其傳給 Streamlit 的 pyplot() 函式即可在 Streamlit app 中顯示 mplfinance 繪製的圖形, 結果如下 :




2025-01-21 補充 : 

今天測試 kbar.py 時發現一個 Bug : 如果在呼叫 plot() 方法時傳入 returnfig 參數會出現 duplicate rturnfig 錯誤, 原因在於傳入 plot() 的 returnfig 會出現在 **kwargs 的字典鍵中, 而 kbar.py 的下列程式碼又傳入 returnfig=True 變成有兩個 returnfig 鍵導致錯誤發生 : 

if embedding:
            fig, ax=mpf.plot(self.df, returnfig=True, **kwargs)

原來的 kbar.py 因為此嵌入用途新增了一個 embedding 參數實屬多此一舉, 應該回歸只使用原生地 returnfig 參數來控制要不要傳回 Figure 物件才對, 茲將 kbar.py 修正如下 :

# kbar.py
import mplfinance as mpf

class KBar():
    def __init__(self, df):
        self.df=df
        self.addplots=[]
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)
        self.addplots.append(plot)
    def plot(self, **kwargs):
        color=mpf.make_marketcolors(up='red', down='green', inherit=True)   
        font={'font.family': 'Microsoft JhengHei'}   
        style=mpf.make_mpf_style(base_mpf_style='default',
                                 marketcolors=color,
                                 rc=font)
        kwargs['type']='candle'
        kwargs['style']=style
        kwargs['addplot']=self.addplots
        if 'returnfig' in kwargs and kwargs['returnfig'] is True:
            fig, axes=mpf.plot(self.df, **kwargs)
            return fig, axes
        else:
            mpf.plot(self.df, **kwargs)

此處先判斷 kwargs 裡面有無 returnfig 參數傳入, 有的話且為 True 表示圖需內嵌故傳回 Figure 物件, 否則就不傳回 Figure 物件, 經測試不管在內嵌或一般使用情境皆可正常繪製圖形. 改用此新版 kbar.py 後, 上面範例程式中的 embedding=True 都要配合改成 returnfig=True.


2025-01-23 補充 : 

今天在進行 kbar.py 打包上傳 PyPi 的準備時重新審視其功能完善性, 發覺在傳入 returnfig=True 時只傳回 Figure 畫布物件似乎不夠完備, 應該連同繪圖區 (軸) 物件串列 Axes 也一起傳回去才對, 這樣在需要進一步自訂繪圖 (例如在主圖上畫一條額外的水平線) 時才有辦法利用 Axes 物件進行加工, 所以進一步修改 kbars.py 為如下 : 

# kbar.py
import mplfinance as mpf

class KBar():
    def __init__(self, df):
        self.df=df
        self.addplots=[]
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)
        self.addplots.append(plot)
    def plot(self, **kwargs):
        color=mpf.make_marketcolors(up='red', down='green', inherit=True)   
        font={'font.family': 'Microsoft JhengHei'}   
        style=mpf.make_mpf_style(base_mpf_style='default',
                                 marketcolors=color,
                                 rc=font)
        kwargs['type']='candle'
        kwargs['style']=style
        kwargs['addplot']=self.addplots
        if 'returnfig' in kwargs and kwargs['returnfig'] is True:
            fig, axes=mpf.plot(self.df, **kwargs)
            return fig, axes 
        else:
            mpf.plot(self.df, **kwargs)

這樣上面的 Gradio 範例程式碼要修改為 : 

# gradio_kbar_2.py 
import gradio as gr
import kbar  # 匯入 kbar
import yfinance as yf  # 匯入 Yahoo Finance 
from talib.abstract import RSI, MFI  # 匯入 Ta-Lib 的技術指標類別

def plot_kbar():
    df=yf.download('0050.tw', start='2024-07-01', end='2024-08-21')  # 取得價量資料
    df.columns=[column.lower() for column in df.columns]  # Ta-Lib 要求欄位名稱為全小寫
    kb=kbar.KBar(df)  # 建立 KBar 物件
    rsi=RSI(df)  # 建立 RSI 指標物件
    mfi=MFI(df, timeperiod=14)  # 建立 MFI 指標物件
    if not rsi.isna().all():  # 確保 RSI 有值
        kb.addplot(rsi, panel=2, ylabel='RSI')
    if not mfi.isna().all():  # 確保 MFI 有值
        kb.addplot(mfi, panel=3, ylabel='MFI')
    # 繪製 K 線圖與技術指標副圖    
    fig, axes=kb.plot(volume=True, mav=[3, 5, 7], returnfig=True, title='台灣五十')  
    return fig  # 傳回 Figure 物件

iface=gr.Interface(
    fn=plot_kbar,  # 繪製函數
    inputs=[],              # 無需用戶輸入
    outputs=gr.Plot(),      # 輸出為 Gradio 的 Plot 物件
    title='Gradio Candle Chart',
    flagging_mode='never',
    )

iface.launch()

而 Streamlit 的範例程式碼也要修改為 :

# streamlit_kbar_2.py
import streamlit as st
import kbar  # 匯入 kbar
import yfinance as yf  # 匯入 Yahoo Finance
from talib.abstract import RSI, MFI  # 匯入 Ta-Lib 的技術指標類別

df=yf.download('0050.tw', start='2024-07-01', end='2024-08-21')  # 取得價量資料
df.columns=[column.lower() for column in df.columns]  # Ta-Lib 要求欄位名稱為全小寫
kb=kbar.KBar(df)  # 建立 KBar 物件
rsi=RSI(df)  # 建立 RSI 指標物件
mfi=MFI(df, timeperiod=14)  # 建立 MFI 指標物件
if not rsi.isna().all():  # 確保 RSI 有值
    kb.addplot(rsi, panel=2, ylabel='RSI')
if not mfi.isna().all():  # 確保 MFI 有值
    kb.addplot(mfi, panel=3, ylabel='MFI')
# 繪製顯示成交量與 3, 5, 7 日均線之 K 線圖
fig, axes=kb.plot(volume=True, mav=[3, 5, 7], returnfig=True)  
st.title("Streamlit Candle Chart")
st.pyplot(fig)

這兩個範例使用新的 kbar.py 測試 OK. 

沒有留言 :