最近在樹莓派 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 套件用法參考 :

沒有留言 :
張貼留言