2026年5月2日 星期六

Python 學習筆記 : 利用語言模型計算技術指標 (二)

前一篇測試中, 我們透過串接 LLM 模型 (OpenAI & Gemini) 要求 AI 生成計算技術指標的程式碼在本機執行運算, 毋須使用例如 Ta-Lib 或 ta, pandas-ta 等技術指標函式庫. 本篇旨在前一篇的基礎上取得 AI 生成的程式碼後算出技術指標 (MACD) 數據, 並繪製 K 線圖. 

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


關於 MACD 技術指標計算, 參考 : 



1. 用 pandas_ta 套件計算 MACD 指標 : 

MACD 由快線 (DIF), 慢線 (DEA) 和 柱狀圖 (Histogram) 三部分組成, 當快速線向上突破訊號線時為買進訊號; 快速線向下跌破訊號線時為賣出訊號. 柱狀圖用來觀察市場動能強弱, 柱狀圖由負轉正表示多方動能變強; 柱狀圖由正轉負表示空方動能變強. 訊號線則是 MACD 快速線的平滑化版本, 用來確認趨勢是否開始反轉. 

MACD 指標預設參數通常是快線 12 日, 慢線 26 日, 信號線 9 日, 以 pandas_ta 套件來說, 呼叫 df.ta.macd() 或 ta.macd() 即可計算這三組 MACD 指標數值. 注意, 在之前測試中呼叫 df.ta.macd() 時傳入 append=True, 這樣 pandas_ta 會將計算出來的 MACD 指標值自動插入 df 的新增欄位中, 預設欄位名稱依序是 MACD_12_26_9 (MACD 線), MACDh_12_26_9 (MACD 柱狀圖), 與 MACDs_12_26_9 (MACD 信號線), 為了後續繪製 K 線圖方便, 這次不使用 append=True 參數, 而是在 df 手動指定傳回的三個 Series 的欄位名稱 :

df[['MACD_line', 'MACD_histogram', 'MACD_signal']]=df.ta.macd(close='Close') 

程式碼如下 : 
 
# ai_stock_test_5.py
import yfinance as yf
import pandas as pd 
import pandas_ta as ta
from kbar import KBar
          
if __name__ == "__main__":
    df=yf.download('0050.tw', start='2024-11-06', end='2025-01-09', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    df[['MACD_line', 'MACD_histogram', 'MACD_signal']]=df.ta.macd(close='Close')
    print(df.tail())
    macd_line_name='MACD_line'       # MACD 線
    macd_signal_name='MACD_signal'   # MACD 訊號線
    macd_histogram_name='MACD_histogram'  # MACD 柱狀圖
    kb=KBar(df)
    histogram_colors=['g' if v >= 0 else 'r' for v in df[macd_histogram_name]]  
    macd_panel=2 
    kb.addplot(
        df[macd_histogram_name],
        panel=macd_panel,
        type='bar',
        color=histogram_colors,
        alpha=0.4,
        ylabel='MACD' # 設定此副圖的 Y 軸標籤
        )
    kb.addplot(
        df[macd_line_name],
        panel=macd_panel, # 必須使用與柱狀圖相同的 Panel
        color='fuchsia', # MACD 線顏色
        width=1.0
        )
    kb.addplot(
        df[macd_signal_name],
        panel=macd_panel, # 必須使用與柱狀圖相同的 Panel
        color='blue',    # 信號線顏色
        width=1.0
        )
    kb.plot(
        volume=True,  # 顯示成交量副圖 (Panel 1)
        mav=(5, 10),  # K 線圖上疊加 5 日和 10 日均線
        title='K 線圖與 MACD 指標'
        )
    
結果如下 :

>>> %Run ai_stock_test_5.py   
[*********************100%***********************]  1 of 1 completed
                Close       High  ...  MACD_histogram  MACD_signal
Date                              ...                             
2025-01-02  46.854988  47.241319  ...       -0.011482     0.202802
2025-01-03  47.325832  47.555217  ...       -0.023311     0.196974
2025-01-06  48.810799  48.847020  ...        0.063469     0.212842
2025-01-07  49.354084  49.740419  ...        0.147264     0.249658
2025-01-08  48.617638  49.100554  ...        0.142791     0.285355

[5 rows x 8 columns]
設定字型為: Microsoft JhengHei
使用指定字型: Microsoft JhengHei
字型候選清單: ['Microsoft JhengHei', 'DejaVu Sans', 'Arial']





2. 串接 OpenAI API 計算 SMA 指標 : 

下面範例是用提示詞要求 LLM 提供 MACD 指標計算函式, 讓本地程式呼叫 exce() 來執行 MACD 計算函式, 最後用 kbar 套件繪製 K 線圖, 提示詞要明確給 LLM 計算 MACD 所需參數, 即快慢線與信號線週期, 另外, 為了後面用 kbar 繪製 K 線圖時取值與運算方便, 還需明確指定 MACD 傳回計算結果時的欄位名稱 :

    計算 MACD 指標, 參數 : fast=12 日, slow=26 日, signal=9 日。
    計算結果的欄位名稱 : MACD_line, MACD_signal, MACD_histogram。
    請注意:務必在 ewm() 函式中設定 min_periods 參數(例如 slow 設為 min_periods=26),
    以確保前期資料不足天數時,算出的結果必須是 NaN。

程式碼如下 : 

# ai_stock_test_6.py
from  openai import OpenAI, APIError 
import yfinance as yf
import pandas as pd
import pandas_ta as ta
from dotenv import dotenv_values
from kbar import KBar

def ask_gpt(
    messages: list[dict[str, str]],
    model: str='gpt-3.5-turbo'
    ) -> str:
    try:
        reply=client.chat.completions.create(
            model=model, 
            messages=messages
            )
        return reply.choices[0].message.content or ''
    except APIError as e:
        return e.message

def ai_helper(df, user_msg):
    role=f'''
        作為一個專業的程式碼生成機器人,
        我需要您的協助來根據特定的用戶需求生成 Python 程式碼。
        為了進行下去,我將提供給您一個遵循格式 {list(df.columns)} 的 DataFrame(df)。
        您的任務是仔細分析用戶的需求並相應地生成 Python 程式碼。
        請注意,您的回應須僅包含代碼本身,並且不應包含任何額外的資訊。
        '''
    # 把 user_msg 加入到 task 的敘述中,讓 AI 知道要算什麼
    task=f'''
        您的任務是開發一個名為 'calculate(df)' 的 Python 函式。
        這個函式應接受一個 DataFrame 作為其參數。確保您僅使用資料集中存在的欄,
        特別是 {list(df.columns)}。        
        用戶的具體運算需求為:【 {user_msg} 】        
        處理後,該函式應返回處理過的 DataFrame。
        您的回應應嚴格包含 'calculate(df)' 函式的 Python 程式碼,
        並排除任何無關的內容。
        '''
    msg=[{"role": "system", "content": role},
         {"role": "user", "content": task}]
    reply_data=ask_gpt(msg)
    # 清理 markdown 語法
    cleaned_code=reply_data.replace("```", "")
    cleaned_code=cleaned_code.replace("python", "")      
    cleaned_code=cleaned_code.strip() # 建議加上 strip() 去除頭尾多餘的空白或換行
    # 傳回程式碼
    return cleaned_code
          
if __name__ == "__main__":
    config=dotenv_values('.env') 
    openai_api_key=config.get('OPENAI_API_KEY')
    client=OpenAI(api_key=openai_api_key)
    df=yf.download('0050.tw', start='2024-11-06', end='2025-01-09', auto_adjust=True)
    df.columns=df.columns.map(lambda x: x[0])
    prompt='''
    計算 MACD 指標, 參數 : fast=12 日, slow=26 日, signal=9 日。
    計算結果的欄位名稱 : MACD_line, MACD_signal, MACD_histogram。
    請注意:務必在 ewm() 函式中設定 min_periods 參數(例如 slow 設為 min_periods=26),
    以確保前期資料不足天數時,算出的結果必須是 NaN。
    '''
    code_str=ai_helper(df, prompt)
    print(code_str)
    exec(code_str)
    df=calculate(df)
    print(df.tail())
    macd_line_name='MACD_line'       # 快速線
    macd_signal_name='MACD_signal'   # 訊號線
    macd_histogram_name='MACD_histogram'  # 柱狀圖
    kb=KBar(df)
    histogram_colors=['g' if v >= 0 else 'r' for v in df[macd_histogram_name]]  
    macd_panel=2 
    kb.addplot(
        df[macd_histogram_name],
        panel=macd_panel,
        type='bar',
        color=histogram_colors,
        alpha=0.4,
        ylabel='MACD' # 設定此副圖的 Y 軸標籤
        )
    kb.addplot(
        df[macd_line_name],
        panel=macd_panel, # 必須使用與柱狀圖相同的 Panel
        color='fuchsia', # MACD 線顏色
        width=1.0
        )
    kb.addplot(
        df[macd_signal_name],
        panel=macd_panel, # 必須使用與柱狀圖相同的 Panel
        color='blue',    # 信號線顏色
        width=1.0
        )
    kb.plot(
        volume=True,  # 顯示成交量副圖 (Panel 1)
        mav=(5, 10),  # K 線圖上疊加 5 日和 10 日均線
        title='K 線圖與 MACD 指標'
        )

執行結果如下 :

>>> %Run ai_stock_test_6.py   
[*********************100%***********************]  1 of 1 completed
def calculate(df):
    fast = 12
    slow = 26
    signal = 9
    
    ema_fast = df['Close'].ewm(span=fast, min_periods=fast).mean()
    ema_slow = df['Close'].ewm(span=slow, min_periods=slow).mean()
    
    macd_line = ema_fast - ema_slow
    signal_line = macd_line.ewm(span=signal, min_periods=signal).mean()
    macd_histogram = macd_line - signal_line
    
    df['MACD_line'] = macd_line
    df['MACD_signal'] = signal_line
    df['MACD_histogram'] = macd_histogram
    
    return df
                Close       High  ...  MACD_signal  MACD_histogram
Date                              ...                             
2025-01-02  46.854992  47.241323  ...     0.237614       -0.050295
2025-01-03  47.325832  47.555217  ...     0.223323       -0.055554
2025-01-06  48.810799  48.847020  ...     0.231812        0.033190
2025-01-07  49.354080  49.740415  ...     0.261899        0.118182
2025-01-08  48.617638  49.100554  ...     0.291621        0.117175

[5 rows x 8 columns]
設定字型為: Microsoft JhengHei
使用指定字型: Microsoft JhengHei
字型候選清單: ['Microsoft JhengHei', 'DejaVu Sans', 'Arial']



但是, 這結果與上面用 pandas_ta 計算出來的有些出入, 圖形大致是一致, 但仔細看 MACD 數據有些誤差, 我詢問 AI 這個 LLM 生成的 calculate() 算法是否正確, 結論是 : 讓 LLM 生成以 Pandas 為基礎的函式, 會與專業金融套件例如 pandas_ta 計算的結果有些出入, 因為它們在底層實作指數移動平均 (EMA) 遞迴公式時, 對第一天的 EMA 該怎麼決定有所分歧, Pandas 會直接拿資料陣列中的第一筆收盤價當作 EMA 的初始種子, 然後從第二天開始套用 EMA 公式; 而 pandas_ta 在計算 12 日 EMA 時會先精準計算前 12 天的簡單移動平均 (SMA)作為第 12 天的 EMA 初始種子, 然後從第 13 天開始才套用 EMA 公式.

在前一篇 SMA 測試不會出現因計算細節分岐產生的錯誤 (因為 SMA 沒有遞迴依賴, 也不需要初始種子), 只要指標名稱裡帶有 "E" (Exponential 指數型, 例如 EMA, MACD) 或者是 "S" / "W" (Smoothed / Weighted 平滑加權型, 例如 RSI, WMA), 在跨套件核對時往往就需要特別去確認底層對第一筆初始值的實作邏輯. 

從以上測試得到一個教訓 : 金融計算應該用專業套件自行編碼, 不要靠提示詞叫 AI 生成, 如果直接執行這些未經審查的程式碼, 那麼計算出來的金融資料可能是錯誤的. 如果用 pandas_ta 一行指令就能搞定的事, 為何要用附帶許多注意事項的提示詞來要求 AI 生成可能計算出錯誤結果的程式碼? 本系列測試將到此為止, 量化投資程式在計算金融資訊部分還是要用專業套件去計算, 不要透過 AI. 

沒有留言 :