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>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
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],
|
||||
}
|
||||
Reference in New Issue
Block a user