Files
stocks-query/scripts/twse-foreign-holdings.py
Timmy 7d72aed984 restore and rebuild all stock query scripts
Recover 7 existing scripts from remote repo and rebuild 5 missing ones:
- twse-margin-trading.py (融資融券)
- twse-day-trading.py (當沖交易)
- twse-foreign-holdings.py (外資持股)
- yahoo-dividend-browserless.py (股利資訊)
- yahoo-broker-trading-browserless.py (主力券商進出)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:07:31 +08:00

129 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
查詢 TWSE 外資持股比例統計
用法:
python3 skills/stocks-query/scripts/twse-foreign-holdings.py 2330
python3 skills/stocks-query/scripts/twse-foreign-holdings.py 3481
資料來源:證交所 TWSE 公開 API外資及陸資持股統計
"""
from __future__ import annotations
import json
import sys
import urllib.request
from datetime import datetime, timedelta
def fetch_foreign_holdings(stock_no: str, date_str: str | None = None) -> dict:
"""從 TWSE 抓外資持股資料,自動往前回溯最多 10 天找到有資料的日期。"""
if date_str:
dates_to_try = [date_str]
else:
today = datetime.now()
dates_to_try = [(today - timedelta(days=i)).strftime("%Y%m%d") for i in range(10)]
for d in dates_to_try:
url = (
f"https://www.twse.com.tw/rwd/zh/fund/MI_QFIIS"
f"?date={d}&selectType=ALL&response=json"
)
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
try:
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode("utf-8", errors="replace"))
except Exception:
continue
if data.get("stat") != "OK":
continue
tables = data.get("tables", [])
if not tables:
fields = data.get("fields", [])
rows = data.get("data", [])
if fields and rows:
tables = [{"fields": fields, "data": rows}]
else:
continue
# 找含「證券代號」或「代號」欄位的表
target_table = None
for tbl in tables:
fields = tbl.get("fields", [])
field_str = "".join(fields)
if "代號" in field_str:
target_table = tbl
break
if not target_table:
if tables:
target_table = tables[0]
else:
continue
fields = target_table["fields"]
# 找代號欄位 index
code_idx = None
for i, f in enumerate(fields):
if "代號" in f:
code_idx = i
break
if code_idx is None:
continue
for row in target_table.get("data", []):
code = row[code_idx].strip()
if code == stock_no:
result = {"date": d}
for i, field in enumerate(fields):
result[field.strip()] = row[i].strip().replace(",", "") if i < len(row) else "-"
return result
raise RuntimeError(f"找不到 {stock_no} 的外資持股資料(已回溯 10 天)")
def print_summary(stock_no: str, d: dict) -> None:
date_fmt = f"{d['date'][:4]}/{d['date'][4:6]}/{d['date'][6:]}"
name = d.get("證券名稱", d.get("名稱", stock_no))
print(f"{name}{stock_no})|外資持股摘要")
print(f"• 日期:{date_fmt}")
# 外資持股
shares = d.get("外資及陸資持股股數", d.get("外陸資持股股數", "-"))
pct = d.get("外資及陸資持股比率", d.get("外陸資持股比率", d.get("持股比率", "-")))
available = d.get("外資及陸資尚可投資股數", d.get("尚可投資股數", "-"))
upper_limit = d.get("外資及陸資共用法令投資上限比率", d.get("投資上限比率", "-"))
total_issued = d.get("發行股數", "-")
# 外資買賣超相關
foreign_buy = d.get("外資及陸資買進股數", d.get("買進股數", "-"))
foreign_sell = d.get("外資及陸資賣出股數", d.get("賣出股數", "-"))
print(f"• 外資持股股數:{shares}")
print(f"• 外資持股比率:{pct}%") if pct != "-" and "%" not in str(pct) else print(f"• 外資持股比率:{pct}")
if total_issued != "-":
print(f"• 發行股數:{total_issued}")
if available != "-":
print(f"• 尚可投資股數:{available}")
if upper_limit != "-":
print(f"• 投資上限比率:{upper_limit}")
if foreign_buy != "-":
print(f"• 外資買進股數:{foreign_buy}")
if foreign_sell != "-":
print(f"• 外資賣出股數:{foreign_sell}")
if __name__ == "__main__":
stock_no = sys.argv[1] if len(sys.argv) > 1 else "2330"
try:
data = fetch_foreign_holdings(stock_no)
print_summary(stock_no, data)
except Exception as e:
print(f"取得失敗:{e}", file=sys.stderr)
sys.exit(1)