2025年8月26日 星期二

台股價量資料套件 twstock 再包裝 (一)

最近在樹莓派 Pi 3 A+ 上安裝 yfinance 雖然成功, 但因為其中一個依賴套件需要 Python 3.9+ 導致匯入失敗, FinMind 與 FinMind 則無法安裝, 於是只好轉而使用 twstock 套件來取得股票資料, 雖然只能取得最近 45 天的價量資料, 但可以取得盤中的即時股價 (有數秒延遲且不保證取得) 不但免費而且也沒有資料量限制, 可說是一個方便的資料獲取工具. 

twstock 的資料範圍可以用下列程式碼得知 : 

>>> import twstock  
>>> stock=twstock.Stock('0050')   
>>> stock.date[0]  
datetime.datetime(2025, 7, 15, 0, 0)   
>>> stock.date[-1]   
datetime.datetime(2025, 8, 26, 0, 0) 

可見 twstock 只能傳回近 45 個交易日的股票資料. 

關於 twstock 用法參考 : 


但是從 twstock 取得之股票價量資料傳回值型態為串列, 如果能將 twstock 再包裝, 設計成像 yfinance 那樣直接傳回 DataFram, 就能直接丟給 mplfinance 或 kbar 繪製 K 圖, 或丟給 Ta-Lib/ta 做技術指標計算, 本篇測試旨在模擬 Yahoo Finance API 的介面, 將 twstock 進行再包裝使其能很方便地銜接後續的量化計算. 

經過兩天來與 ChatGPT 協作, 來回修改原始碼後初步打磨出下面這個 twstock_data.py 模組, 先期目標是要模仿 yfinance 的 download() 函式介面, 將 twstock 取得之股票價量資料打包成與新版 Yahoo Finannce API 一樣的多層欄位架構 DataFrame 後傳回, 程式碼如下 : 

# twstock_data.py
import twstock
import pandas as pd
from datetime import datetime

def download(stocks, start=None, end=None):
    """
    模擬 yfinance.download(),使用 twstock 抓取台股資料
    stocks: '0050.tw' 或 ['0050.tw','0056.tw'], '0050' 或 ['0050','0056']
    start, end: 'YYYY-MM-DD' 字串
    回傳 DataFrame,欄位結構完全模擬 yfinance (MultiIndex: Price, Ticker)
    """
    if isinstance(stocks, str):    # 判斷標的是單支或多支股票
        stocks=[stocks]    # 若是單支則將其放入串列
    all_data=[]    # 儲存所有價量資料的串列
    for stock_code in stocks:    # 走訪所有標的
        code=stock_code.replace('.tw', '').replace('.TW','')    # 去除 tw/TW
        stock=twstock.Stock(code)    # 建立 Stock 物件
        # 收集資料
        data=[]    # 儲存打包後的股票價量資料的串列
        for date, o, h, l, c, v in zip(stock.date, stock.open, stock.high, stock.low, stock.close, stock.capacity):    # 打包成 tuple 後存入串列, 最後轉成 DataFrame
            data.append((date, o, h, l, c, v, c))  # 最後一個 c 當 Adj Close
        df=pd.DataFrame(data, columns=['Date','Open','High','Low','Close','Volume','Adj Close'])
        df.set_index('Date', inplace=True)    # 設定索引
        # 篩選日期
        if start:    # 有傳入起始日期
            start_date=datetime.strptime(start, '%Y-%m-%d')
            df=df[df.index >= start_date]
        if end:    # 有傳入結束日期
            end_date=datetime.strptime(end, '%Y-%m-%d')
            df=df[df.index <= end_date]
        # 建立 MultiIndex 欄位 (Price, Ticker)
        df.columns=pd.MultiIndex.from_product([df.columns, [stock_code.upper()]], names=["Price", "Ticker"])
        all_data.append(df)
    # 合併多檔股票
    result=pd.concat(all_data, axis=1).sort_index(axis=1, level=0)
    return result

此模組的 download() 函式可以傳入單支股票 (第一參數 stocks 傳入股票代號字串例如 '0050.tw' 或 '0050.TW'); 也可以傳入多支股票 (股票代號字串組成的 list), 股票代號則是模仿 yfinance 後面有 '.tw' 或 '.TW'. 儲存在 Stock 物件中的各日期股票價量資料會在 for 迴圈中先存入元組串列中, 然後轉成 DataFrame 傳回. 注意, twstock 並沒有 Adj Close (調整權息後的收盤價) 欄位, 此處將 Close 當作 Adj Close 以便與 yfinance 格式相容. 

測試如下, 首先下載單支股票 :

>>> import twstock_data as td  
>>> df=td.download('0050.tw', start='2025-07-14', end='2025-08-14')  
>>> df 
Price      Adj Close   Close    High     Low    Open     Volume
Ticker       0050.TW 0050.TW 0050.TW 0050.TW 0050.TW    0050.TW
Date                                                           
2025-07-15     50.00   50.00   50.10   49.33   49.35   69996070
2025-07-16     50.40   50.40   50.70   50.15   50.15   92296251
2025-07-17     50.70   50.70   50.80   50.30   50.60   89861784
2025-07-18     51.45   51.45   51.60   51.30   51.40  107920505
2025-07-21     50.90   50.90   51.10   50.75   51.10   78953424
2025-07-22     50.55   50.55   51.30   50.45   50.90   92162175
2025-07-23     50.70   50.70   50.85   50.55   50.60   65118595
2025-07-24     51.00   51.00   51.10   50.80   51.00   80703644
2025-07-25     50.90   50.90   51.10   50.80   51.00   40414405
2025-07-28     51.05   51.05   51.40   50.90   51.20   53609331
2025-07-29     50.45   50.45   51.05   50.25   51.05  107016033
2025-07-30     51.00   51.00   51.10   50.65   50.80   84052507
2025-07-31     51.55   51.55   51.60   51.10   51.10   83010418
2025-08-01     51.10   51.10   51.20   50.65   50.80  103281333
2025-08-04     50.80   50.80   50.80   50.40   50.65   88936360
2025-08-05     51.45   51.45   51.45   51.05   51.25   48253208
2025-08-06     51.00   51.00   51.10   50.85   51.10   51398358
2025-08-07     52.45   52.45   52.60   51.65   51.65  127953769
2025-08-08     52.45   52.45   52.60   52.30   52.55   61743128
2025-08-11     52.85   52.85   53.10   52.05   52.35   74255676
2025-08-12     52.90   52.90   53.00   52.55   52.85   60122164
2025-08-13     53.35   53.35   53.40   52.95   53.15   78177118
2025-08-14     53.15   53.15   53.30   52.95   53.20   59818527

檢視欄位可知, 結構為與 yfinance 相同的多層欄位結構, 此處為兩層, 第一層為價量欄位 (Price); 第二層為標的 (Ticker) :

>>> df.columns  
MultiIndex([('Adj Close', '0050.TW'),
            (    'Close', '0050.TW'),
            (     'High', '0050.TW'),
            (      'Low', '0050.TW'),
            (     'Open', '0050.TW'),
            (   'Volume', '0050.TW')],
           names=['Price', 'Ticker'])

與新版 yfinance 的傳回值一樣, 如果要用 mplfinance 繪製 K 線圖, 必須將 DataFrame 的多層欄位改回單層欄位 (去除 Ticker 欄位, 僅留 Price 欄位) :

>>> df.columns=df.columns.map(lambda x: x[0])   # 改回單層欄位
>>> df.columns   
Index(['Adj Close', 'Close', 'High', 'Low', 'Open', 'Volume'], dtype='object')

可見只有單層的 Price 欄位了 :

>>> df 
            Adj Close  Close   High    Low   Open     Volume
Date                                                        
2025-07-15      50.00  50.00  50.10  49.33  49.35   69996070
2025-07-16      50.40  50.40  50.70  50.15  50.15   92296251
2025-07-17      50.70  50.70  50.80  50.30  50.60   89861784
2025-07-18      51.45  51.45  51.60  51.30  51.40  107920505
2025-07-21      50.90  50.90  51.10  50.75  51.10   78953424
2025-07-22      50.55  50.55  51.30  50.45  50.90   92162175
2025-07-23      50.70  50.70  50.85  50.55  50.60   65118595
2025-07-24      51.00  51.00  51.10  50.80  51.00   80703644
2025-07-25      50.90  50.90  51.10  50.80  51.00   40414405
2025-07-28      51.05  51.05  51.40  50.90  51.20   53609331
2025-07-29      50.45  50.45  51.05  50.25  51.05  107016033
2025-07-30      51.00  51.00  51.10  50.65  50.80   84052507
2025-07-31      51.55  51.55  51.60  51.10  51.10   83010418
2025-08-01      51.10  51.10  51.20  50.65  50.80  103281333
2025-08-04      50.80  50.80  50.80  50.40  50.65   88936360
2025-08-05      51.45  51.45  51.45  51.05  51.25   48253208
2025-08-06      51.00  51.00  51.10  50.85  51.10   51398358
2025-08-07      52.45  52.45  52.60  51.65  51.65  127953769
2025-08-08      52.45  52.45  52.60  52.30  52.55   61743128
2025-08-11      52.85  52.85  53.10  52.05  52.35   74255676
2025-08-12      52.90  52.90  53.00  52.55  52.85   60122164
2025-08-13      53.35  53.35  53.40  52.95  53.15   78177118
2025-08-14      53.15  53.15  53.30  52.95  53.20   59818527

關於新版 Yahoo Finance API 參考 :


這樣就可以將 df 丟給 mplfinance 或 kbar 套件繪製 K 線圖了 (用 kbar ) :

>>> from kbar import KBar   
>>> kb=KBar(df)   
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)   

結果如下 : 




關於 kbar 套件用法參考 : 


沒有留言 :