先日、子供をお風呂に入れている20分ほどの間に、中東情勢の緊迫化を受けてドル円が162円台後半まで急伸していました。。。上がったら乗ろうと思っていたのに、スマホを置いていた僕は完全に置いていかれました。「重要そうなニュースが出た瞬間だけ教えてくれる仕組みがあれば」と本気で思ったのが、今回調べ始めたきっかけです。
僕は普段、ファンダメンタルズのニュースを四六時中追えるほど暇じゃありません(というか子育て中でそんな余裕はゼロです)。以前LINE通知やDiscord Webhookでテクニカル指標のアラートを組んだことはあったのですが、今回はニュース見出しそのものをリスクスコアリングして、「本当にヤバそうな時だけ」通知を飛ばす仕組みをPythonで試してみました。
キーワード検索だけでは足りない理由
最初は「Iran」「FRB」「介入」みたいなキーワードを見出しに含むかどうかでフラグを立てようとしました。でもすぐに限界に気づきます。「停戦合意」というキーワードは本来ポジティブですが、「停戦崩れる懸念」だと真逆の意味になる。単語の有無だけを見ていると、こうした文脈の反転を拾えません。見出し全体の意味を理解した上で、方向感と強さをスコアにしてくれる仕組みが必要だと思いました。
LLM APIで見出しをスコアリングする設計
そこで考えたのが、ニュース見出しをLLM APIに渡して「ドル円への影響度」を -1.0(強い円高要因)〜 +1.0(強い円安要因)のスコアで返してもらう方法です。RSSやニュース検索APIで見出しを定期的に取得し、1件ずつスコアリングして、絶対値が一定の閾値を超えたものだけをDiscordに通知する、という流れにしました。
import feedparser
RSS_URLS = [
"https://news.google.com/rss/search?q=USDJPY+OR+ドル円&hl=ja&gl=JP",
"https://news.google.com/rss/search?q=Iran+OR+FRB+OR+oil+price&hl=en-US&gl=US",
]
def fetch_headlines():
headlines = []
for url in RSS_URLS:
feed = feedparser.parse(url)
headlines += [entry.title for entry in feed.entries[:10]]
return headlines
def score_headline(headline: str) -> dict:
"""LLM APIに見出しを渡してリスクスコアを返してもらう"""
prompt = f"""
以下のニュース見出しについて、USD/JPY相場への影響を
-1.0(強い円高要因) 〜 1.0(強い円安要因) のスコアで評価し、
JSON形式で {{"score": float, "reason": str}} のみを返してください。
見出し: {headline}
"""
return call_llm_api(prompt) # 各自のLLM APIクライアントに置き換え
def check_and_alert(threshold: float = 0.6):
for headline in fetch_headlines():
result = score_headline(headline)
if abs(result["score"]) >= threshold:
send_discord_alert(
f"⚠️ {headline}\nスコア: {result['score']}({result['reason']})"
)
if __name__ == "__main__":
check_and_alert()
call_llm_apiとsend_discord_alertは、以前書いたDiscord Webhookの記事のコードをそのまま流用しています。見出し単位でスコアを出すので、複数件のニュースが同時に来た時は平均や最大値を取るなど、集計方法は好みで調整するとよさそうです。
実際に使ってみた感想と注意点
1週間ほど回してみた感じ、キーワード検索よりは明らかに誤検知が減りました。ただしLLM APIの呼び出しにはコストと若干のレイテンシがかかるので、超短期スキャルピングのような秒単位の判断には向きません。あくまで「大きめのニュースを見逃さないための早期警戒システム」という位置づけで、これだけで自動売買の発注シグナルにするのはまだ怖いというのが正直な感想です。
まとめ
ニュース見出しのスコアリングは、キーワード検索の「文脈を読めない」弱点をかなり補ってくれる印象です。僕自身、これのおかげで「お風呂中に置いていかれる」事故は少し減りました。次はスコアの閾値やRSSソースをもう少しチューニングしつつ、実際のポジション判断にどこまで組み込めるか、しばらく検証を続けてみようと思います。

