報告要比較三份收盤表?用 skfolio 把公開資料整理成兩張比較表
📌 數據僅限本工具現階段參考用。本文只示範工具怎麼安裝、設定、執行,不是投資建議,也不推薦任何股票。
重點摘要
- skfolio 1.12.4 已成功安裝,工具記錄本次原始碼安裝耗時 22.4 秒。
- 證交所2026-09-01至2026-10-29三個示範代號對齊後共38列價格,缺值為0。
- 38列價格轉成37列每日變化後,PearsonDistance 輸出兩張3×3矩陣,示範程式存成CSV。
- 相關矩陣與pandas逐格核對一致;距離對角線出現約1.05e-8浮點殘差,以1e-7容許誤差檢查通過。
- 結論: 本次實測成功把三欄公開價格轉成兩張可存檔的比較矩陣。
- 適合: 需要使用Python整理公開收盤資料並保存矩陣檔的人
- 不適合: 期待不寫程式就能使用完整桌面介面的人
| 實測項目 | 結果 |
|---|---|
| 資料來源 | 證交所公開STOCK_DAY;資料截至2026-10-29 |
| 安裝 | 22.4秒;Python3.12.14 |
| 價格輸入 | 38列×3欄,0缺值 |
| 每日變化輸入 | 37列×3欄 |
| 輸出 | correlation.csv及distance.csv,各3×3 |
| 交叉核對 | 相關表與pandas一致,atol=1e-12 |
收盤後要整理一份資料報告,桌上卻開著三張股票收盤表。日期得對齊,每天的變化得另外整理,換個期間又要重做。這次實測 skfolio 的 PearsonDistance:把同一段期間的公開價格交給它,產出相關表與距離表,再存成可留在報告旁的 CSV。這裡教的是資料整理與工具操作,代號只是示範輸入。
下載
來源是 skfolio 官方專案,授權為 BSD-3-Clause。README 要求 Python 3.10 以上;本次實際使用 Python 3.12.14、skfolio 1.12.4,測試設備是 Mac mini M4 Pro(12 核、64GB)。為了重現同一份程式,以下固定到本次受測提交,而不是永遠追最新版。
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'git+https://github.com/skfolio/skfolio.git@82114d911cbfc581c2e807903bcc70297bbcff13'
套件會安裝 NumPy、pandas、SciPy、scikit-learn、Plotly 等依賴。本次從原始碼安裝成功,工具記錄為 22.4 秒;下載速度不同,時間也會不同。這個功能不需要券商登入、API 金鑰或下載語言模型。
設定
本文示範資料是證交所公開資料,使用個股日成交資訊 STOCK_DAY 端點,查詢月份為 2026 年 9 月與 10 月,最後保留到 2026-10-29。當時最新完整交易日是 10 月 29 日,沒有把 10 月 30 日盤中資料混進來。
示範輸入為 2330、2317、2412。資料清理包含民國日期轉西元、移除收盤價千分逗號、按日期排列,再把三份表依日期合併。本次得到 38 個共同日期、3 欄價格、0 個缺值。價格是公開原始收盤價,沒有另外作除權息還原處理;日期與來源不同,輸出自然可能不同。

跑起來
將下列完整實測程式存為 test_tool.py,再執行。第一次會下載並保存六份原始 JSON;再次執行沿用本地快取,避免查核時資料版本變動。public 是存檔資料夾,無須啟動網站。
python test_tool.py
import json,time,urllib.request
from pathlib import Path
import pandas as pd
import numpy as np
import skfolio
from skfolio.distance import PearsonDistance
from skfolio.preprocessing import prices_to_returns
codes=['2330','2317','2412']
series=[]
Path('public').mkdir(exist_ok=True)
for code in codes:
rows=[]
for month in ['20260901','20261001']:
url=f'https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date={month}&stockNo={code}'
cache=Path(f'public/twse_{code}_{month}.json')
d=json.loads(cache.read_text()) if cache.exists() else json.load(urllib.request.urlopen(url,timeout=40))
assert d['stat']=='OK',d['stat']
Path(f'public/twse_{code}_{month}.json').write_text(json.dumps(d,ensure_ascii=False))
for r in d['data']:
y,m,day=map(int,r[0].split('/'));date=f'{y+1911:04}-{m:02}-{day:02}'
if date<='2026-10-29': rows.append((date,float(r[6].replace(',',''))))
time.sleep(1)
series.append(pd.Series(dict(rows),name=code))
p=pd.concat(series,axis=1).sort_index()
assert p.index[-1]=='2026-10-29'
missing=int(p.isna().sum().sum()); assert missing==0
p.to_csv('public/prices.csv',index_label='Date')
x=prices_to_returns(p)
t=time.perf_counter();est=PearsonDistance().fit(x);elapsed=time.perf_counter()-t
corr=pd.DataFrame(est.codependence_,index=codes,columns=codes)
distance=pd.DataFrame(est.distance_,index=codes,columns=codes)
corr.to_csv('public/correlation.csv');distance.to_csv('public/distance.csv')
assert np.allclose(corr,x.corr(),atol=1e-12)
assert np.allclose(distance,distance.T) and np.allclose(np.diag(distance),0,atol=1e-7)
result={'version':skfolio.__version__,'source':'TWSE public STOCK_DAY','start':p.index[0],'end':p.index[-1],'codes':codes,'price_rows':len(p),'change_rows':len(x),'missing':missing,'matrix_shape':list(corr.shape),'fit_seconds':round(elapsed,6),'pandas_crosscheck':'PASS','symmetry_and_diagonal':'PASS at atol=1e-7','max_diagonal':float(np.max(np.abs(np.diag(distance)))),'correlation':corr.round(6).values.tolist(),'distance':distance.round(6).values.tolist()}
Path('public/result.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
print(json.dumps(result,ensure_ascii=False,indent=2));print('OUTPUT prices.csv, correlation.csv, distance.csv')
這次 prices.csv 有 38 列價格。prices_to_returns 轉換成每天相對前一天的變化,因此少了沒有前一天可比的第一列,剩下 37 列。再把這份資料交給 PearsonDistance().fit(),從 codependence_ 與 distance_ 取得兩張 3×3 表,輸出成 correlation.csv、distance.csv。CSV 的寫檔與網頁排版由本次示範程式完成,並非套件自帶的報告網站。
| 實測項目 | 結果 |
|---|---|
| 資料期間 | 2026-09-01 至 2026-10-29 |
| 對齊後價格 | 38 列 × 3 欄,缺值 0 |
| 每日變化 | 37 列 × 3 欄 |
| 兩張輸出表 | 各 3×3,另存 CSV |
| 相關表交叉核對 | 與 pandas 計算逐格一致,容許誤差 1e-12 |

影片使用的金融工作簿頁面,是我們寫的唯讀檢視器,讀取同一份實測輸出;不是 skfolio 原生操作介面。畫面顯示三位小數,CSV 保留更多位數。這裡不解讀格子中的數字,也不把這個小範例當作完整的研究流程。
常見問題
為什麼 38 天只剩 37 列? 第一筆價格沒有前一天可比較,轉換後會少一列;這次不是下載漏了一天。若換成別的代號,先檢查共同日期和缺值,不要直接填零。
距離表的對角線為什麼不是精準的零? 第一次驗證時,出現約 1.05367e-08 的殘差,令預設的近零比較失敗。相關表同一格是 0.9999999999999998,後續開根號會放大浮點誤差。確認原始輸出後,我把距離檢查設為 atol=1e-7,沒有改原始結果,也沒有在 CSV 裡強制補零。
資料抓不到怎麼辦? 程式會檢查證交所回傳的 stat;未成功就停止,不能把錯誤頁當價格。先核對網路、月份與代號,再看保存的 JSON。這次端點成功回傳六份資料,不需要登入。
適合願意使用 Python、定期將公開資料整理成矩陣檔的人;如果期待點兩下就有完整桌面軟體,還需要另外設計介面。我會保留這段整理流程,下次換資料期間時再用,同時重新核對日期與缺值。這次沒有測大型資料、其他作業系統或完整投資組合功能。
專案:skfolio/skfolio(BSD-3-Clause)。功能參考:PearsonDistance 官方文件;資料來源:證交所個股日成交資訊。