為了像電影主角一樣利用AI分析資料提供男主角我有辦法財富自由,未來可以打照出有實體的 ai情人
把有關金融分析工具不斷做修正和一下大升級
**LINE 股市 AI 助理:錯誤韌性大升級—台股 ETF 代碼正規化、金價雙來源回退、新聞 API 防呆
**
今天把「查不到、回不來、整段爆掉」的三大痛點一次清掉:
1. 台股 ETF/特別股代碼(像 00937B、00929、2881A)全面自動補 .TW;
2. 金價用台銀頁面為主、期貨+匯率為備援;
3. 新聞 API 全面韌性化,就算掛了也不影響整體回覆。外加翻譯模式行為修復。
⸻
本日目標
• 使用者丟「00937b」「00929」「0050」都能正確查到(不再出現 possibly delisted)。
• 「金價」不再顯示「暫無法取得」,改為主來源 + 備援。
• 新聞來源失敗不會拖垮整份股票報告。
• 翻譯模式(翻譯->英文 / 翻譯->結束)行為直覺、穩定。
⸻
台股代碼除了純數字,還常見尾碼字母(ETF、特別股、權證),例如 00937B。
做法很簡單:凡是「4–6 碼數字 + 可選 1 個英文字母」,一律補上 .TW。
def _normalize_ticker_input(user_text: str) -> str:
s_up = user_text.strip().upper()
# 2330 / 0050 / 00937B / 2881A → *.TW
if re.fullmatch(r"\d{4,6}[A-Z]?", s_up):
return f"{s_up}.TW"
return s_up # NVDA / AAPL / ^GSPC 等維持原樣
✅ 這段放在 app_fastapi.py 的股票入口判斷前,就能讓 YahooStock、yfinance 一次吃到正確代碼。
⸻
Yahoo 的 quote API 有時會挑 header 或短暫 40x/50x。
在 my_commands/stock/YahooStock.py 我們做了兩件事:
• 標準瀏覽器 UA(降低被擋風險)
• 簡單 Retry(3 次、遞增等待)
DEFAULT_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Accept": "application/json",
}
def _retry(times=3, delay=1.2):
def deco(fn):
def wrap(*a, **kw):
last = None
for i in range(times):
try:
return fn(*a, **kw)
except Exception as e:
last = e
time.sleep(delay * (i + 1))
raise last
return wrap
return deco
✅ 初始化失敗不拋例外、只把欄位設為 None,確保分析流程不中斷。
⸻
主來源:台灣銀行黃金牌價(賣出價,TWD/克)
台銀頁面表格偶爾調整,我們不鎖死欄位順序,而是掃表、找含「賣出」欄位與「黃金/1公克/牌價」關鍵字的列。
def _parse_tbb_html_to_price_twd_per_gram(html: str) -> Optional[float]:
soup = BeautifulSoup(html, "html.parser")
for table in soup.find_all("table"):
# 找 header 取得「賣出」欄位索引
...
# 掃行,找「黃金/1公克/牌價」等關鍵字
...
return float(value) # TWD/克
return None
備援:COMEX 金期貨 + USD/TWD 匯率 → 換算 TWD/克
• GC=F(美元/盎司)
• 1 troy oz = 31.1034768 g
• 乘上即時 USD→TWD 匯率
def xau_twd_per_gram_fallback() -> Optional[float]:
px = yf.Ticker("GC=F").fast_info.get("last_price") or ...
usdtwd = _fetch_usdtwd_rate() # open.er-api.com
return round(px * usdtwd / 31.1034768, 2) if px and usdtwd else None
✅ 先爬台銀,失敗再退 GC=F + 匯率。兩個都失敗才回覆「暫時無法取得」。
⸻
cnyes 會偶爾擋或變更回應,requests.get(...).json() 直接丟會導致 JSONDecodeError。
做法:先 text,再手動 json.loads;若失敗就回空字串,不拋例外。
def stock_news(keyword: str, limit: int = 5) -> str:
try:
url = f"https://ess.api.cnyes.com/ess/api/v1/news/keyword?q={keyword}&limit={limit}&page=1"
r = requests.get(url, timeout=6, headers={"User-Agent": UA})
if r.status_code != 200 or not r.text.strip().startswith("{"):
return ""
data = json.loads(r.text)
...
return "\n".join(lines)
except Exception:
return "" # 失敗就安靜地給空字串
✅ 主流程永遠能產生分析報告;沒有新聞就寫「(新聞來源暫時無法取得)」。
⸻
if low.startswith("翻譯->"):
lang = msg.split("->", 1)[1].strip()
if lang == "結束":
translation_states.pop(chat_id, None)
return reply("✅ 已結束翻譯模式")
translation_states[chat_id] = lang
return reply(f"🌐 已開啟翻譯 → {lang},請直接輸入要翻的內容。")
...
if chat_id in translation_states:
out = await translate_text(msg, translation_states[chat_id])
return reply(f"🌐 ({translation_states[chat_id]})\n{out}")
⸻
功能 測試指令 預期
ETF 代碼自動補 .TW 00937b、00929、0050 正常產生報告,不再出現 delisted/no timezone
台股/美股指數 台股大盤、美股大盤 產出大盤分析
金價 金價 / 黃金 有價格;主來源台銀,失敗時顯示備援來源
匯率 JPY 顯示 1 JPY ≈ TWD 的即時估值
新聞失敗容錯 暫時關網路 / 擋 cnyes 報告仍能產出,新聞段落顯示替代文字
翻譯 翻譯->英文 → 任意句子 → 翻譯->結束 翻譯模式進出清楚
⸻
⸻
⸻
今日成果範例(摘錄)
• 查 00937b:成功回覆 00937B.TW 的即時資訊與分析。
• 金價:即使台銀表格異動,依舊能回覆「每克 XXX 元(備援來源)」的快訊。
• 翻譯->英文:一句話即回,翻譯->結束 乾淨退場。
⸻