2025年9月28日 星期日

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

在前一篇測試中, 我們撰寫了一個自訂模組 twstock_data.py, 其中的 download() 函式模仿 yfinance.download() 透過 twstock 模組取得最近 45 天的台股量價資料, 回傳格式也是仿新版 yfinance 的雙層欄位的 DataFrame, 參考 : 


本篇則是要擴充 twstock_data.py 模組, 添加如下實用函式 : 
  • available_range(stock) : 傳回指定股票的可用日期範圍
  • available_ranges(stocks) :  傳回多支股票 (串列) 的可用日期範圍 (串列)
  • latest_close(stock) : 指定股票的最新收盤價
程式碼修改如下 : 

# 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

def available_range(stock):
    """回傳單支股票可用日期範圍"""
    code=stock.replace('.tw', '').replace('.TW','')
    st=twstock.Stock(code)
    return st.date[0], st.date[-1]

def available_ranges(stocks):
    """回傳多支股票的可用日期範圍 dict"""
    if isinstance(stocks, str):
        code=stocks.replace('.tw', '').replace('.TW','')
        stocks=[code]
    result={}
    for s in stocks:
        code=s.replace('.tw', '').replace('.TW','')
        st=twstock.Stock(code)
        result[s]=(st.date[0], st.date[-1])
    return result

def latest_close(stock):
    """回傳最新收盤價"""
    code=stock.replace('.tw', '').replace('.TW','')
    st=twstock.Stock(code)
    return st.price[-1]

在互動環境測試如下 :

>>> import twstock_data as td   

呼叫 available_range() 傳入單一股票會傳回起訖日期的 datetime 物件 tuple : 

>>> td.available_range('0050.tw')   
(datetime.datetime(2025, 8, 15, 0, 0), datetime.datetime(2025, 9, 26, 0, 0))

呼叫 available_ranges() 傳入多支股票串列會傳回各標的之起訖日期字典 :

>>> td.available_ranges(['0050.tw', '2330.tw'])   
{'0050.tw': (datetime.datetime(2025, 8, 15, 0, 0), datetime.datetime(2025, 9, 26, 0, 0)), '2330.tw': (datetime.datetime(2025, 8, 15, 0, 0), datetime.datetime(2025, 9, 26, 0, 0))}

呼叫 latest_close() 傳入單一股票會傳回最近之收盤價 : 

>>> td.latest_close('0050.tw')   
57.25

依據上面 available_range() 的起訖日期下載價量資料 : 

>>> df=td.download('0050.tw', start='2025-08-15', end='2025-09-26')    
>>> df  
Price      Adj Close   Close    High     Low    Open     Volume
Ticker       0050.TW 0050.TW 0050.TW 0050.TW 0050.TW    0050.TW
Date                                                           
2025-08-15     53.15   53.15   53.30   52.80   53.15   55175623
2025-08-18     53.30   53.30   53.35   52.80   52.95   48004775
2025-08-19     53.20   53.20   53.30   53.05   53.30   31232902
2025-08-20     51.70   51.70   52.60   51.50   52.55  241376498
2025-08-21     52.00   52.00   52.10   51.75   51.75   76525276
2025-08-22     51.75   51.75   52.10   51.65   52.05   47853232
2025-08-25     52.70   52.70   52.95   52.35   52.45   64451775
2025-08-26     52.75   52.75   52.85   52.30   52.55   42402036
2025-08-27     53.00   53.00   53.15   52.90   53.00   91463591
2025-08-28     52.50   52.50   52.90   52.50   52.80   61856984
2025-08-29     52.50   52.50   52.95   52.50   52.80   37614042
2025-09-01     52.00   52.00   52.45   51.75   52.15   82748402
2025-09-02     52.00   52.00   52.50   51.95   52.30   34866736
2025-09-03     51.95   51.95   52.20   51.75   51.90   41247523
2025-09-04     52.30   52.30   52.70   52.25   52.55   38611419
2025-09-05     52.85   52.85   52.90   52.65   52.70   38976905
2025-09-08     53.35   53.35   53.55   53.25   53.40   61781569
2025-09-09     53.90   53.90   53.95   53.50   53.55   57948913
2025-09-10     54.95   54.95   55.00   54.40   54.40   75585424
2025-09-11     55.45   55.45   55.90   55.25   55.55   87937880
2025-09-12     56.00   56.00   56.00   55.70   55.75   59101728
2025-09-15     56.00   56.00   56.05   55.80   56.00   56031630
2025-09-16     56.75   56.75   56.85   56.15   56.15   65388179
2025-09-17     56.35   56.35   56.60   56.25   56.60   70590054
2025-09-18     56.95   56.95   56.95   56.25   56.50   45899106
2025-09-19     57.00   57.00   57.10   56.60   57.00   74098514
2025-09-22     57.40   57.40   57.40   56.75   57.00   59170020
2025-09-23     58.45   58.45   58.60   57.85   57.85   92241987
2025-09-24     58.40   58.40   58.95   58.10   58.70   95128957
2025-09-25     58.20   58.20   58.40   58.00   58.35   59962136
2025-09-26     57.25   57.25   57.90   56.85   57.90  170962034
>>>

可見最近一個交易日 9/26 的收盤價確實是 57.25.

沒有留言 :