Lin.Log|Studio
// 01 · claude-daily-settlement-report$ cat claude-daily-settlement-report.md

讓 Claude 替我每天看持倉新聞

2026.05.12技術筆記8 分鐘閱讀
讓 Claude 替我每天看持倉新聞

技術主題: 持倉日報 + Yahoo 股市新聞 + Claude AI 評語
實作時間: 2026/05
工時估計: 約半天
紀錄者: _YLin_Lai
參考資料:
- 原 repo: https://github.com/hu0937/FinPilot
- Anthropic Python SDK: https://github.com/anthropics/anthropic-sdk-python


每天 20:00,strategy_monitor.py 推一則 Telegram:「2330 在 PEG 策略持倉落榜了。」我看到,然後不知道該怎麼辦。因為我不知道其他 15 個策略怎麼看 2330,也不知道最近外資投信有沒有在出貨,更不知道有沒有什麼新聞我沒注意到。這篇記的是我怎麼把這個問題解掉——用一份每日 AI 持倉日報,在 21:00 幫我把所有訊號整合好。

1.主題

FinPilot 既有的 strategy_monitor.py 每天 20:00 推 Telegram,但只監控 PEG 和處置股 2 個策略。我想要一份「持倉日報」——對每個我目前持有的台股,整合 16 個策略的 exit signal、最新月營收、5 日法人流向、Yahoo 股市最新新聞,然後讓 Claude 給我一句 80 字內的建議評語。21:00 自動推 Telegram。

新增 3 個檔案:

  • agents/daily_settlement_report.py — 主程式,負責 fan-out 和排程
  • core/news_fetcher.py — Yahoo 股市新聞抓取與解析
  • core/settlement_analyzer.py — 分級邏輯、Claude prompt 組裝、Telegram 訊息格式化

整個作業大約半天完成,包含 Yahoo parser 重寫的時間。

2.背景(需求簡介)

現在的決策痛點:我看到 PEG 策略某天「持倉 2330 落榜了」的推播,但我不知道——

  • 2330 在其他 15 個策略下的狀態如何?是全都覺得該出場,還是只有 PEG 一個轉弱?
  • 最近 5 天外資投信買賣超怎麼走?
  • 最新月營收 YoY 是漲是跌?
  • 有沒有什麼關稅、Roadmap、財報相關的新聞是我沒注意到的?

要做這個判斷,每次手動查 5 個來源加上 16 個策略——10 分鐘起跳,而且我不一定每天都有那個時間。我想要一份每日報告幫我把這些訊號預先整合好,讓我直接看結論。

這個需求的規格很清楚:每天 21:00 推 Telegram,每個持倉一段評語,不用精確,能省掉我 80% 的查找時間就夠了。

3.架構

資料流走 4 個步驟:

21:00 cron 觸發
  |
1. core.database.get_positions(market='TW') 抓持倉
2. 對每持股 fan-out:
   a. settlement_analyzer.gather_stock_stats() — 策略命中 / 月營收 / 法人 5 日累積
   b. news_fetcher.fetch_news() — Yahoo 股市最新 5 則
   c. build_claude_prompt() -> Claude API -> 1~2 句評語
3. classify_holding() 紅黃綠分級
4. format_telegram_message() -> notifier.send_message()

每個持股一次 Claude call,假設持股 20 檔,每天約 20 次 API 呼叫。sonnet-4-6 model 的 cost 估算下來大概 $0.24/天,也就是約 $7/月。我覺得可以接受——這是一個每天省 10 分鐘手動查找的工具,$7 換每月 5 小時,划算。

模型設定用既有的 BOT_CLAUDE_MODEL 環境變數(預設 sonnet-4-6),max_tokens=120temperature=0.3。temperature 壓低是為了讓評語格式穩定,不要每次都長得不一樣。

4.紅黃綠分級

對每個持股算 exit_ratio,定義為「不命中的策略數除以總策略數」:

exit_ratio = (total_strategies - hit_count) / total_strategies
  • 紅:exit_ratio >= 0.6,超過 60% 的策略不再命中 — 「建議觀察出場」
  • 黃:0.3 <= exit_ratio < 0.6,介於中間 — 「持有觀察」
  • 綠:exit_ratio < 0.3,大多數策略仍命中 — 「建議持有」

ETF 是邊際 case。個股策略 universe 本來就不含 ETF,0050 / 00757 這類標的天生就是 16/16 不命中,但這不代表它們該出場——策略對 ETF 從來就沒有意見。第一版先標為「白 N/A」,讓報告不要誤判 ETF。Phase 2 再設計 ETF 專屬的判斷邏輯,例如用 NAV 折溢價或追蹤誤差來評估。

這個分級邏輯很簡單,但它把「要不要深入看」的決策成本從 10 分鐘壓到 3 秒——看顏色就知道今天要不要認真研究這個部位。

5.Yahoo 新聞抓取的踩雷

news_fetcher.py 是這次最花時間的部分,雖然功能看起來只是「抓 5 則新聞標題」。

第一次寫 parser,我假設 Yahoo 股市用的 DOM 結構是:

items = soup.find_all('li', class_='story_item')
for item in items:
    title = item.find('a', class_='news-title').get_text()
    time_text = item.find('span', class_='time-ago').get_text()

這是我憑經驗猜的 class name。寫好 fixture HTML,寫了 3 個 unit test,全綠。然後實打 Yahoo——返回 0 則新聞。

抓真實 HTML 回來看 DOM,發現實際結構完全不同:

  • 列表項目是 <li class="js-stream-content">,不是 story_item
  • 標題在 <h3> 內的 <a class="mega-item-header-link">,不是 news-title
  • 時間欄位根本不在 HTML 裡——Yahoo 用 JavaScript render「3 小時前」這種相對時間,server side 回傳的是空的 placeholder

修了 selector,標題抓到了,時間還是拿不到。我退而求其次,從 <p> summary 的內文裡用 regex 找 YYYY-MM-DD HH:MM 格式的時間字串當 fallback。抓不到時間就留空,至少標題進來了,Claude 也能拿來讀。

同時把 fixture HTML 從我猜的結構換成從真實頁面截出來的 DOM 片段,3 個 unit test 重跑——OK。

事後的心得:不要用「我猜它應該長這樣」來寫 HTML parser 的 fixture。fixture 應該是從真實 response 截取的,否則 unit test 通過只是在測試自己的假設,不是在測試真實系統。這次浪費了大約 40 分鐘在修 selector,主因就是 fixture 跟真實 DOM 不一致。

6.Claude prompt 設計

對每個持股,我用 build_claude_prompt() 組一份結構化的 prompt:

你是台股投資助理。以下是 2330 台積電 的最新狀態:

— 持倉資訊 —
持有股數:1000 股
平均成本:500.00
當前價:1100.0
未實現損益:+120.0%

— 策略訊號 —
16 個通過回測的策略中,當前命中:3 個
仍建議持有:s01, s05, s12
已不再命中:s03, s10, s111, s116, s118

— 基本面 —
最新月營收 (2026-04):410,725,118 千元 (YoY +17.5%)

— 籌碼面(最近 5 個交易日累積)—
外資:+5,000,000 股
投信:+200,000 股
自營商:-100,000 股

— 個股新聞(最近 5 則)—
1. 台積電 4 月營收創高
2. 美關稅政策評估
...

請輸出 1~2 句中文建議評語(80 字內)。

prompt 分四節:持倉資訊、策略訊號、基本面、籌碼面,最後加新聞。每節獨立,讓 Claude 能跨節整合——例如「3 個策略命中 + 外資連買 5 天 + 月營收 YoY +17.5%」這三個訊號來自不同節,但 Claude 會把它們合在一起給評語。

實測對 00757 統一 FANG+ 的回覆:

「建議觀察,暫緩加碼。雖然自營商籌碼明顯偏多、帳面小幅獲利,但 12 個回測策略目前零命中,技術面動能訊號全面熄火,建議持股不動、設好停利點,待策略訊號重新點亮再評估是否加碼或出場。」

這個品質我滿意。它把「籌碼偏多但策略全熄火」的矛盾點說清楚了,還給了一個具體行動建議,比我自己查 5 個來源再想半天得出的結論品質差不多,但時間從 10 分鐘壓到 5 秒。

prompt 有幾個設計選擇值得說明:

給結構化資料,不要餵原始文。我刻意把策略列表、財務數字、籌碼分開成獨立的節,而不是把 DB query 的 raw output 直接貼進去。結構化輸入讓 Claude 的回覆也更有結構,如果直接把 log dump 給它,輸出會雜亂、重點也不清楚。

temperature=0.3。這個參數讓評語格式穩定。如果設到 0.8 以上,有時候會得到散文式的長段落,有時候得到條列式清單,不一致。0.3 讓每次輸出都是 1~2 句話的固定格式,Telegram 訊息排版也更乾淨。

max_tokens=120。80 字的中文評語大概是 120 token,設這個 cap 是為了防止 Claude 突然給我一篇分析報告——偶爾 temperature 低仍然可能輸出超長回覆,這個 cap 是硬約束。

7.心得

AI prompt 給結構化資料,不要餵原始文。把策略、財務、籌碼、新聞分四節打包進 prompt,Claude 評語就會跨節整合。如果直接把 raw log dump 給它,輸出會雜亂,有用的訊號會被淹掉。資料整理的工作要在 prompt 組裝這一層做,不是推給 Claude 去做。

邊際 case 是發現的,不是想的。ETF 的邊際 case 我寫 spec 的時候沒想到——直到實際跑,看到「12/12 不命中」才意識到 ETF 本來就不在策略 universe 內、這個數字沒有意義。第一個版本不需要完美,跑到看見邊際 case 才好修。試著在 spec 階段把所有邊際 case 都想清楚,反而是在浪費時間猜測。

Token cost 比想像中便宜。$7/月 跑一個每日 AI 評語 agent,對我來說比手動查找 10 分鐘乘以 30 天划算太多了。在決定要不要加 Claude call 的時候,先算一下 cost,不要用「AI 很貴」當預設假設——對大多數個人投資決策輔助的使用情境,cost 根本不是瓶頸,時間才是。

結語:

這篇講 21:00 每日 AI 持倉日報——資料整合、Claude 評語、Telegram 推播。下一篇 #5(系列最後一篇)講 FastAPI 監控 dashboard,跟我踩到 9.6 GB DB 的 COUNT(*) 雷的故事。


本系列文章源自我部署的 FinPilot 專案。原專案由 hu0937 開源並維護,是一個整合 Telegram Bot + Claude AI + APScheduler 的台股量化分析平台。本次的歷程是「部署 + 擴充」,所有原始的 Bot 指令、策略 daemon、sim() 回測架構都來自原作者的設計。

原專案:https://github.com/hu0937/FinPilot

感謝原作者把這麼完整的 base 開源出來,讓我可以站在這個地基上加東西。

相關紀錄

linlog@studio $ exit

有東西想一起做出來?

[聯繫我]
email dannylaii@linlogstudio.com
hours Mon – Fri · After 7PM
base Taiwan · 遠端優先
Connection to lin.log closed. © 2026 Lin.Log|Studio