#!/usr/bin/env python3 """ 查詢 TWSE 個股融資融券餘額(信用交易統計) 用法: python3 skills/stocks-query/scripts/twse-margin-trading.py 2330 python3 skills/stocks-query/scripts/twse-margin-trading.py 3481 資料來源:證交所 TWSE 公開 API """ from __future__ import annotations import json import sys import urllib.request from datetime import datetime, timedelta def fetch_margin(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/marginTrading/MI_MARGN" 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: continue # 找到個股那張表(title 含「融資融券」或欄位含「股票代號」) target_table = None for tbl in tables: fields = tbl.get("fields", []) if "股票代號" in fields: target_table = tbl break if not target_table: continue fields = target_table["fields"] for row in target_table.get("data", []): code = row[fields.index("股票代號")].strip() if code == stock_no: result = {"date": d} for i, field in enumerate(fields): result[field.strip()] = row[i].strip().replace(",", "") 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("股票名稱", stock_no) print(f"{name}({stock_no})|融資融券摘要") print(f"• 日期:{date_fmt}") # 融資 margin_buy = d.get("融資買進", "-") margin_sell = d.get("融資賣出", "-") margin_cash = d.get("融資現償", "-") margin_balance = d.get("融資餘額", d.get("融資今日餘額", "-")) margin_limit = d.get("融資限額", "-") print(f"• 融資買進:{margin_buy}") print(f"• 融資賣出:{margin_sell}") print(f"• 融資現償:{margin_cash}") print(f"• 融資餘額:{margin_balance}") if margin_limit != "-": print(f"• 融資限額:{margin_limit}") # 融券 short_sell = d.get("融券賣出", "-") short_buy = d.get("融券買進", "-") short_cash = d.get("融券現償", "-") short_balance = d.get("融券餘額", d.get("融券今日餘額", "-")) short_limit = d.get("融券限額", "-") print(f"• 融券賣出:{short_sell}") print(f"• 融券買進:{short_buy}") print(f"• 融券現償:{short_cash}") print(f"• 融券餘額:{short_balance}") if short_limit != "-": print(f"• 融券限額:{short_limit}") # 券資比 try: mb = float(margin_balance) if margin_balance != "-" else 0 sb = float(short_balance) if short_balance != "-" else 0 if mb > 0: ratio = (sb / mb) * 100 print(f"• 券資比:{ratio:.2f}%") except (ValueError, ZeroDivisionError): pass if __name__ == "__main__": stock_no = sys.argv[1] if len(sys.argv) > 1 else "2330" try: data = fetch_margin(stock_no) print_summary(stock_no, data) except Exception as e: print(f"取得失敗:{e}", file=sys.stderr) sys.exit(1)