# 每天留一份股價紀錄：用 ta 把公開價格表加上平均欄

- 網址：https://blog.valenvan.uk/posts/bukosabino-ta.html
- 發布日期：2026-10-29
- 測試設備：Mac mini M4 Pro（12 核、64GB）
- 專案：bukosabino/ta（https://github.com/bukosabino/ta），MIT 授權
- 測試版本：ta 0.11.0 / a890410710a6e483c9ba08da7f3dd5089e4b9dff
- 實測影片：https://www.youtube.com/shorts/0XDExj64b18
- 分類：金融工具區（https://blog.valenvan.uk/finance.html）

> 📌 數據僅限本工具現階段參考用。本文只示範工具怎麼安裝、設定、執行，不是投資建議，也不推薦任何股票。

## 重點摘要

- ta 0.11.0 在 Python 3.12.14 以證交所2026-09-01至2026-10-28公開資料37筆產出CSV。
- 輸出有Date、Close、SMA5、SMA20、RSI14共5欄，其中3個是新增計算欄位。
- fillna=False時，三個計算欄開頭分別保留4、19、13格空白。
- 本輪沿用成功實錄，另以標準函式獨立核對185個來源值及75個計算值，全部通過。
- **結論：** 固定設定可把37筆公開收盤資料輸出成含三個計算欄位的CSV。
- **適合：** 收盤後想留每日價格與近期平均紀錄，且可依教學執行Python的人
- **不適合：** 需要點選式網站介面的人

| 實測項目 | 結果 |
|---|---|
| 公開資料日期 | 2026-09-01至2026-10-28 |
| 資料列數 | 37 |
| 新增計算欄位 | 3 |
| 來源數值核對 | 185個相同 |
| SMA20初期空白 | 19格 |

收盤後想留一份紀錄，日後打開就能同時查到當天價格與最近幾天的平均。從證交所下載的原始表有每天的價格，卻不會附上自己想要的五日平均、二十日平均；若用試算表補公式，每天增加資料時還得確認新的一列也有算到。這才是本次使用 ta 的起點：把固定計算步驟保存，之後換資料再產表。

這是提供給會執行 Python、想保留固定資料整理步驟的人的方法。ta 是計算套件，沒有自己的試算表視窗；影片中的深藍表格是本片自製的 CSV 檢視與金融排版，讀取真實輸出，並非套件原生介面。開頭「每天留紀錄」是使用情境說明，不是使用者訪談或已執行的每日排程。本次修訂沿用 10 月 29 日既有核心實測和錄影，沒有把重剪說成重跑。

## 下載

來源是 [bukosabino/ta](https://github.com/bukosabino/ta)。受測版本 0.11.0，原始碼提交 `a890410710a6e483c9ba08da7f3dd5089e4b9dff`，使用 Python 3.12.14，主要依賴 pandas、NumPy；下載公開資料另用 requests。測試設備為 Mac mini M4 Pro（12 核、64GB）。

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install ta==0.11.0 pandas==3.1.0 numpy==2.5.4 requests==2.34.2
```

## 設定

**本文示範資料是證交所公開資料，資料日期為 2026-09-01 至 2026-10-28，共 37 筆日資料。** 10 月 28 日是本次查詢時最近一個已完成的交易日。資料不是套件自己抓的：本次腳本呼叫證交所 `STOCK_DAY` 公開端點，分兩個月份取得 JSON，再交給 ta。沒有使用帳戶、券商登入或金鑰。2330 只是示範輸入代號。

要成功讀取，先把民國日期轉成西元，按日期排序，將價格與成交量的千分逗號去掉。這些整理步驟放在下面的完整腳本。原始檔保留供核對，不用手動補上不存在的交易日。

![輸入的證交所收盤資料](https://blog.valenvan.uk/img/bukosabino-ta-article_1.png)

## 跑起來

把下面存為 `demo.py`，再執行。時間範圍刻意固定，方便重現；改作日常工作時，請同步修改月份和截止日期，並確認已取得收盤後的完整資料。

```bash
python demo.py
```

```python
import json,csv,time,sys
from pathlib import Path
import requests,pandas as pd,numpy as np
from ta.trend import SMAIndicator
from ta.momentum import RSIIndicator
rows=[]
for month in ['20260901','20261001']:
 r=requests.get('https://www.twse.com.tw/exchangeReport/STOCK_DAY',params={'response':'json','date':month,'stockNo':'2330'},timeout=40)
 r.raise_for_status();raw=r.json();assert raw['stat']=='OK',raw.get('stat')
 Path('twse-'+month+'.json').write_text(json.dumps(raw,ensure_ascii=False))
 for a in raw['data']:
  y,m,d=map(int,a[0].split('/'));date=f'{y+1911}-{m:02}-{d:02}'
  if date<='2026-10-28':rows.append([date,*[float(a[i].replace(',','')) for i in [3,4,5,6,1]]])
 time.sleep(2)
df=pd.DataFrame(rows,columns=['Date','Open','High','Low','Close','Volume']).drop_duplicates('Date').sort_values('Date')
assert df.iloc[-1].Date=='2026-10-28','latest completed trading day missing'
assert not df.isna().any().any()
df.to_csv('input.csv',index=False)
t=time.perf_counter();out=df[['Date','Close']].copy()
out['SMA5']=SMAIndicator(df.Close,window=5,fillna=False).sma_indicator()
out['SMA20']=SMAIndicator(df.Close,window=20,fillna=False).sma_indicator()
out['RSI14']=RSIIndicator(df.Close,window=14,fillna=False).rsi()
assert out.SMA5.isna().sum()==4
assert out.SMA20.isna().sum()==19
assert out.RSI14.isna().sum()==13
assert np.allclose(out.SMA5.dropna(),df.Close.rolling(5).mean().dropna())
assert np.allclose(out.SMA20.dropna(),df.Close.rolling(20).mean().dropna())
out.to_csv('indicators.csv',index=False,float_format='%.4f')
print('TWSE PUBLIC DATA | 2330 demonstration input')
print(f'DATA {df.Date.iloc[0]} to {df.Date.iloc[-1]} | {len(df)} daily rows | missing input 0')
print('OUTPUT indicators.csv | columns: Date, Close, SMA5, SMA20, RSI14')
print('WARMUP blank cells: SMA5=4, SMA20=19, RSI14=13 | fillna=False')
print('SMA5 and SMA20 independently checked with pandas rolling: PASS')
print(f'Calculation and CSV write: {time.perf_counter()-t:.4f} seconds')
print(out.tail(3).to_string(index=False))
print('Python',sys.version.split()[0])

```

執行後會留下兩個月的原始 JSON、`input.csv` 和 `indicators.csv`。後者依序有 Date、Close、SMA5、SMA20、RSI14 五欄，保留日期與收盤，新增三個計算欄位。這次計算到 CSV 寫出約 0.0071 秒；不含資料下載和安裝，不能拿來當端到端時間。

| 實測項目 | 結果 |
| --- | --- |
| 輸入 | 37 列，日期與五個數值欄 |
| 輸出 | 37 列、5 欄，新增 3 個計算欄位 |
| 開頭保留空白 | SMA5 為 4 格、SMA20 為 19 格、RSI14 為 13 格 |
| 原始數值核對 | 185 個來源數值與 CSV 相同 |
| 獨立重算 | 兩種平均及 RSI 已逐格核對至 CSV 的四位小數容許差 |

![實際輸出的計算欄位與暖機空格](https://blog.valenvan.uk/img/bukosabino-ta-article_2.png)

## 常見問題

**前面為什麼空白？** 本次明確指定 `fillna=False`。二十日平均要累積二十筆輸入，所以前十九格不會有值；這是這次設定的結果。CSV 裡是空欄位，影片檢視器以長橫線顯示，沒有把它補成零。原始輸入數值沒有缺漏，與計算初期的空格是兩回事。

**下載不到怎麼辦？** 腳本會檢查 HTTP 回應及 `stat`；沒有回傳 OK 就停止，不把空資料繼續算下去。先檢查查詢月份與官方服務狀態，避免密集重複請求。本次抓取成功，沒有宣稱重現過官方服務中斷。

**能直接點兩下操作嗎？** 這是 Python 套件，讀者需要能建立環境及執行腳本。適合固定整理價格資料、希望保存計算步驟的人；只想開網站看現成表格的人，不適合直接拿它當網站使用。這次驗證上述三欄及 CSV 輸出，其他指標、即時更新及長期排程沒有驗證。

每天需要這份紀錄時，可以保留設定；下次先核對新資料，再重新產表。這是使用建議，本次沒有測試每日自動更新。它的實際用途是把重複計算留在程式裡，讓輸出格式保持一致；本文不解讀示範數值。原測試有成功收據；本輪另將 185 個來源數值和 75 個計算值以標準函式獨立核對，四位小數容許差 0.00006，全部通過。完整資料收據與腳本隨本次成品保留。

專案：[bukosabino/ta](https://github.com/bukosabino/ta)；授權：[MIT](https://github.com/bukosabino/ta/blob/a890410710a6e483c9ba08da7f3dd5089e4b9dff/LICENSE)。官方 API 說明：[ta 文件](https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html)。資料：[證交所個股日成交資訊](https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date=20261001&stockNo=2330)。
