floviq logo floviq
← 筆記 · 2026-05-11 · reference

TWSE T86「外陸資買賣超股數」欄位命名實況 — silent failure 風險為何容易發生

TWSE 開放資料 API 的 T86 endpoint 提供當日三大法人 (外資 / 投信 / 自營商) 買賣超數據。財經新聞口語普遍以「外資買賣超」稱呼此數字,但 API 回傳的欄位名稱跟口語並不一致,用關鍵字部分比對 (substring keyword match) 抓欄位會有 silent failure 風險。本文整理欄位命名實況、silent failure 機制,以及穩定取值的建議做法。


① Endpoint 與欄位實況

GET https://www.twse.com.tw/rwd/zh/fund/T86?date={YYYYMMDD}&selectType=ALL&response=json

回傳當日各檔股票的外資 / 投信 / 自營商買賣超,每個交易日 17:30 後更新。

回傳結構 (fields 陣列順序,0-based index,共 19 欄):

  • [0] 證券代號
  • [1] 證券名稱
  • [2] 外陸資買進股數(不含外資自營商)
  • [3] 外陸資賣出股數(不含外資自營商)
  • [4] 外陸資買賣超股數(不含外資自營商)對應財經新聞口語「外資買賣超」(目標欄位)
  • [5] 外資自營商買進股數
  • [6] 外資自營商賣出股數
  • [7] 外資自營商買賣超股數
  • [8] 投信買進股數
  • [9] 投信賣出股數
  • [10] 投信買賣超股數
  • [11] 自營商買賣超股數
  • [12] 自營商買進股數(自行買賣)
  • [13] 自營商賣出股數(自行買賣) · [14] 自營商買賣超股數(自行買賣)
  • [15] 自營商買進股數(避險) · [16] 自營商賣出股數(避險) · [17] 自營商買賣超股數(避險)
  • [18] 三大法人買賣超股數

目標的「外陸資買賣超股數(不含外資自營商)」在 0-based index 4:index 2 是「外陸資買進股數」(買進總量,非買賣超淨額),index 3 是「外陸資賣出股數」。另外欄位中沒有單獨的「自營商買進股數」,自營商買進只存在 (自行買賣) (index 12) 與 (避險) (index 15) 兩個帶後綴的版本。

② 命名實況 · 字串細節

完整欄位名稱:

外陸資買賣超股數(不含外資自營商)
  • 字數:17 個字 (含括號)
  • 括號類型:半形 () · 不是全形 ()
  • 「外陸資」= 外資 + 陸資合稱
  • 「不含外資自營商」= 排除外資旗下自營業務 · 才是外部法人對台股的真實淨方向

口語的「外資買賣超」/「法人外資買超」/「外資淨買超」都不是 API 的實際欄位名

③ 為什麼欄位名稱跟口語不同

TWSE T86 endpoint 同時追蹤多個主體:

  • 外資 (含外資自營商)
  • 外資 (不含外資自營商) ← 財經新聞講的「外資」對應此欄位
  • 外資自營商
  • 投信
  • 自營商

為避免歧義,每個欄位名都明確標註所涵蓋的範圍。完整名稱是「外陸資買賣超股數(不含外資自營商)」,不是「外資買賣超」,也不是「外資淨買超」。

④ Silent failure 風險為何容易發生

開發者抓 T86 資料時,若用關鍵字部分比對 (例如搜「外資」拿第一個命中) 抓欄位,可能會抓到:

  • 外資自營商買進股數 (index 5,外資旗下自營業務,跟外部法人的進出方向不同)
  • 外陸資買進股數(不含外資自營商) (index 2,用「外資」比對時,因括號內「不含外資自營商」也含「外資」字串,第一個命中往往落在這裡,抓到的是買進總量而非買賣超淨額)
  • 其他細分欄位 (投信 / 自營商各組的買進、賣出、買賣超)

T86 欄位中並不存在標記「合計」或「(含外資自營商)」的彙總欄位。若需要「含外資自營商」口徑,要自行把 index 4 (外陸資買賣超,不含) 與 index 7 (外資自營商買賣超) 相加;官方唯一的彙總欄位是 index 18「三大法人買賣超股數」。

抓錯欄位的後果:

  • T86 的數值是帶千分位逗號的字串 (例如 '186,758,969',型別為字串),不是數字。直接做算術會得到 NaN 或字串串接,取值後必須先 .replace(/,/g, '') 去逗號再 Number() 轉換
  • 即使索引抓錯 (例如抓到買進而非買賣超),取出的仍是一個合法字串,不會拋出錯誤
  • 流程照樣完整跑完,推播照樣送出
  • 數字本身看起來合理,沒有 null / undefined
  • 只有人工比對官網數字才會發現抓錯欄位

這是典型的 silent failure:自動化流程持續穩定執行,但執行的是錯的事。錯誤堆疊看不到、靜態檢查看不到、單元測試若用了錯的測試資料也救不了。

另一個 silent failure 來源:全形括號 () 與半形 () 不同,字串比對會直接落空;若把備援邏輯寫成「找不到就用預設 index 0」,反而會無聲地抓到 證券代號

⑤ 穩定取值的建議做法

做法 1:對齊完整欄位名稱

// 用完整欄位名
const idx = fields.indexOf('外陸資買賣超股數(不含外資自營商)');
if (idx === -1) {
  throw new Error('Expected field not found in T86 fields');
}
const value = dataRow[idx];

indexOf 找不到時直接拋出錯誤,不要無聲地退回預設值。

做法 2:固定 index (需鎖定欄位結構)

T86 此欄位在 fields 陣列的 index 4 (0-based)。若確認 endpoint 結構穩定,可直接用 index:

// 對齊 T86 fields[4]「外陸資買賣超股數(不含外資自營商)」
const raw = dataRow[4];
const value = Number(raw.replace(/,/g, ''));  // 數值為帶逗號字串,先去逗號再轉數字

但這需要先確認 endpoint 結構沒變,或加上結構驗證:

const expected = '外陸資買賣超股數(不含外資自營商)';
if (fields[4] !== expected) {
  throw new Error(`T86 schema drift · expected fields[4] = "${expected}" got "${fields[4]}"`);
}

⑥ Edge case 已知

  • 假日 / 國定假日:回傳 { "stat": "很抱歉,沒有符合條件的資料!", "total": 0 },注意句中是全形逗號「,」(U+FF0C) 而非半形,且沒有 fields / data 欄位;最穩的判斷方式是 stat !== 'OK',而不是比對整句字面值
  • 回應慢:尖峰時段 (9-10 點) 約 5-8 秒,逾時設定 ≥ 15000ms,並加上 3 次重試
  • 日期格式:此 endpoint 只接受西元 8 碼 YYYYMMDD。傳民國 7 碼 (例如 1150508) 會被當成西元年 1150,回 {"stat":"查詢日期小於101年05月02日,請重新查詢!","total":0}
  • 編碼:UTF-8,內含較多全形空白與中文,取值時用 fields 陣列 index 比對較穩

⑦ 為什麼這個設計值得知道

對於用 n8n 或自建流程抓 T86 資料的開發者,欄位命名不對齊口語是一個常見的 silent failure 入口。對齊完整欄位名稱並加上結構驗證,才能避免推播錯誤數字;「沒有拋出錯誤」不等於「跑對了」,自動化的可靠性建立在每個欄位都人工對齊過一次真實來源,而不是靠關鍵字猜。


修正紀錄

先前版本有以下更正:

  • §①:目標欄位 index 2 → index 4(index 2=買進、3=賣出、4=買賣超),並補上完整 19 欄對照。
  • §①:無單獨的「自營商買進股數」欄位,只有(自行買賣) index 12 與(避險) index 15。
  • §④:T86 無「含外資自營商」彙總欄位,該口徑需 index 4 + index 7 自行相加,官方唯一彙總為 index 18。
  • §④:抓錯欄位取出的是帶逗號字串,非數字,需去逗號再轉數字。
  • §⑤:做法 2 索引 dataRow[2] → dataRow[4]。
  • §⑥:假日的 stat 字串為全形逗號,實務以 stat !== 'OK' 偵測較穩。
  • §⑥:date 僅接受西元 8 碼 YYYYMMDD,不接受民國 7 碼。

※ 本文描述 TWSE T86 endpoint API 欄位結構與 silent failure 風險 · 不構成投資建議 · 投資請自行評估風險。

資料來源:TWSE OpenAPI T86