Files
AH-Oil-Trader/core/analysis/news.py
T
Axel HocksandClaude Opus 4.8 75d28827e8 Initial commit: Oil Trading Bot (MT5, WTI)
Headless FastAPI-Backend (server.py + core/engine.py) mit Mobile-PWA (web/),
Strategie-/Backtest-Suite und Doku. Secrets, DB, Logs und Laufzeit-State sind
via .gitignore ausgeschlossen; Config-Vorlage: oil_widget_config.ini.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 08:29:23 +02:00

77 lines
3.2 KiB
Python

"""
core/analysis/news.py — Keyword-basiertes News-Sentiment
"""
from __future__ import annotations
# Bullische Phrasen für WTI (Angebot ↓ / Nachfrage ↑ / Risiko ↑)
NEWS_BULLISH_KW = {
"supply cut", "production cut", "opec cut", "opec+ cut", "output cut",
"sanction", "embargo", "ban on", "blockade", "shutdown", "outage",
"disruption", "halt", "force majeure", "pipeline attack",
"attack", "strike on", "missile", "drone strike", "tension escalat",
"iran tension", "houthi", "red sea", "strait of hormuz", "war",
"conflict escalat", "retaliat", "threat",
"demand growth", "demand surge", "demand rise", "demand strong",
"stockpile draw", "inventory draw", "stocks drop", "stocks fall",
"stockpiles fall", "crude draw", "cushing draw", "eia draw",
"pipeline shutdown", "refinery fire", "gulf of mexico storm",
"hurricane", "winter storm", "cold snap",
"oil surge", "oil rally", "oil jump", "oil soar", "oil spike",
"crude rise", "crude rally", "wti rise", "wti surge", "wti rally",
}
# Bärische Phrasen für WTI (Angebot ↑ / Nachfrage ↓ / Entspannung)
NEWS_BEARISH_KW = {
"supply glut", "oversupply", "production increase", "output rise",
"production boost", "opec boost", "opec+ unwind", "spr release",
"strategic reserve release", "saudi increase",
"us production record", "shale boom", "permian growth",
"demand drop", "demand fall", "demand weak", "demand slump",
"recession", "slowdown", "weak economy", "china slowdown",
"stockpile build", "inventory build", "stocks rise", "stocks build",
"crude build", "stockpiles rise", "cushing build", "eia build",
"ceasefire", "truce", "deal reached", "agreement", "diplomatic",
"talks resume", "easing tension", "sanction lift", "sanction relief",
"oil drop", "oil plunge", "oil slide", "oil fall", "oil decline",
"crude drop", "crude plunge", "wti fall", "wti slide", "oil crash",
}
def calc_news_sentiment(headlines: list, half_life_hours: float = 8.0) -> dict:
"""
Bewertet Headlines per Keyword-Matching mit altersgewichtetem Decay.
score: -1.0 (klar bärisch) … +1.0 (klar bullisch)
"""
import time as _time
if not headlines:
return {"score": 0.0, "n_bull": 0.0, "n_bear": 0.0, "samples": []}
now = _time.time()
bull_w = bear_w = 0.0
samples = []
for h in headlines:
text = (h.get("title_original") or h.get("title") or "").lower()
if not text:
continue
age_h = max(0.0, (now - h.get("ts", now)) / 3600.0)
weight = 0.5 ** (age_h / max(1.0, half_life_hours))
b = sum(1 for kw in NEWS_BULLISH_KW if kw in text)
s = sum(1 for kw in NEWS_BEARISH_KW if kw in text)
if b > s:
bull_w += weight * (1 + 0.3 * (b - 1))
samples.append(("bull", h.get("title", "")[:70]))
elif s > b:
bear_w += weight * (1 + 0.3 * (s - 1))
samples.append(("bear", h.get("title", "")[:70]))
total = bull_w + bear_w
score = 0.0 if total < 0.1 else (bull_w - bear_w) / total
return {
"score": max(-1.0, min(1.0, score)),
"n_bull": round(bull_w, 1),
"n_bear": round(bear_w, 1),
"samples": samples[:5],
}