Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1af18f1029 | |||
| 05295f4b2a | |||
| bcc19577eb | |||
| ef7ea500a9 | |||
| d6694d97a3 |
Binary file not shown.
@@ -0,0 +1,39 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
# 🪙 Bitcoin Trading Signal System
|
||||||
|
|
||||||
|
Ein intelligentes Trading-Signal-System für Bitcoin, das **technische Analyse (MACD)** mit **News-Sentiment-Analyse** kombiniert, um fundierte Kauf- und Verkaufsempfehlungen zu generieren.
|
||||||
|
|
||||||
|
## 📋 Features
|
||||||
|
|
||||||
|
### 🔍 Technische Analyse
|
||||||
|
- **MACD-Indikator** (Moving Average Convergence Divergence)
|
||||||
|
- Erkennung von Bullish/Bearish Crossovers
|
||||||
|
- Histogramm-Analyse für Momentum-Erkennung
|
||||||
|
- Trend-Analyse über multiple Zeitperioden
|
||||||
|
|
||||||
|
### 📰 Sentiment-Analyse
|
||||||
|
- Echtzeit-Analyse von Bitcoin-News
|
||||||
|
- Keyword-basierte Sentiment-Bewertung
|
||||||
|
- Multiple News-Quellen (CryptoCompare, optional NewsAPI)
|
||||||
|
- Aggregierung von positiven/negativen Marktsignalen
|
||||||
|
|
||||||
|
### 🎯 Kombinierte Signale
|
||||||
|
- Gewichtete Kombination aus MACD + Sentiment
|
||||||
|
- 5 Signal-Stufen: Starker Kauf, Kauf, Halten, Verkauf, Starker Verkauf
|
||||||
|
- Konfidenz-Bewertung für jedes Signal
|
||||||
|
- Detaillierte Begründungen für Empfehlungen
|
||||||
|
|
||||||
|
## 🚀 Installation
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
- Python 3.8 oder höher
|
||||||
|
- pip (Python Package Manager)
|
||||||
|
|
||||||
|
### 1. Dependencies installieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd bitcoin_trading
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Optional: NewsAPI-Schlüssel
|
||||||
|
|
||||||
|
Für erweiterte News-Analyse kannst du einen kostenlosen NewsAPI-Schlüssel erhalten:
|
||||||
|
|
||||||
|
1. Registriere dich auf [NewsAPI.org](https://newsapi.org)
|
||||||
|
2. Hole dir deinen API-Schlüssel
|
||||||
|
3. Verwende ihn mit `--newsapi-key` Parameter
|
||||||
|
|
||||||
|
## 💻 Verwendung
|
||||||
|
|
||||||
|
### Basis-Analyse (empfohlen)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python bitcoin_trader.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Dies führt eine vollständige Analyse durch mit:
|
||||||
|
- Aktuellen Bitcoin-Preisdaten
|
||||||
|
- 30 Tage historische MACD-Daten
|
||||||
|
- Aktuelle News-Sentiment-Analyse
|
||||||
|
- Kombinierter Trading-Empfehlung
|
||||||
|
|
||||||
|
### Erweiterte Optionen
|
||||||
|
|
||||||
|
**Ausführliche Ausgabe mit Ladestatus:**
|
||||||
|
```bash
|
||||||
|
python bitcoin_trader.py --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mehr historische Daten (z.B. 60 Tage):**
|
||||||
|
```bash
|
||||||
|
python bitcoin_trader.py --days 60
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schnelles Signal (nur Empfehlung):**
|
||||||
|
```bash
|
||||||
|
python bitcoin_trader.py --quick
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mit NewsAPI-Schlüssel:**
|
||||||
|
```bash
|
||||||
|
python bitcoin_trader.py --newsapi-key YOUR_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alle Optionen kombiniert:**
|
||||||
|
```bash
|
||||||
|
python bitcoin_trader.py --verbose --days 90 --newsapi-key YOUR_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Als Python-Modul verwenden
|
||||||
|
|
||||||
|
```python
|
||||||
|
from bitcoin_trading import BitcoinTrader
|
||||||
|
|
||||||
|
# Initialisiere Trader
|
||||||
|
trader = BitcoinTrader(verbose=True)
|
||||||
|
|
||||||
|
# Führe Analyse durch
|
||||||
|
trader.run_analysis(days=30)
|
||||||
|
|
||||||
|
# Oder hole schnelles Signal
|
||||||
|
signal = trader.get_quick_signal()
|
||||||
|
print(signal)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Output-Beispiel
|
||||||
|
|
||||||
|
```
|
||||||
|
╔══════════════════════════════════════════════════════════════════╗
|
||||||
|
║ BITCOIN TRADING SIGNAL - 2024-12-02 15:30 ║
|
||||||
|
╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
📊 EMPFEHLUNG: 🟢 KAUF
|
||||||
|
💯 KONFIDENZ: 72%
|
||||||
|
💰 AKTUELLER PREIS: $42,583.50
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📈 TECHNISCHE ANALYSE (MACD)
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Signal: KAUF
|
||||||
|
MACD: 125.34
|
||||||
|
Signal Line: 98.21
|
||||||
|
Histogram: 27.13
|
||||||
|
Preis-Änderung (10 Tage): +5.67%
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📰 SENTIMENT-ANALYSE
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Markt-Sentiment: POSITIV
|
||||||
|
Sentiment-Score: 0.425
|
||||||
|
Analysierte Artikel: 28
|
||||||
|
├─ Positiv: 16
|
||||||
|
├─ Neutral: 8
|
||||||
|
└─ Negativ: 4
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📋 BEGRÜNDUNG
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
1. MACD-Signal: KAUF (Konfidenz: 70%)
|
||||||
|
2. Bullish Crossover: MACD kreuzt Signal-Linie von unten
|
||||||
|
3. Positives Momentum: Histogramm steigt
|
||||||
|
4. Markt-Sentiment: POSITIV (Konfidenz: 75%, Score: 0.425)
|
||||||
|
5. ✅ MACD und Sentiment stimmen überein → Starkes Signal
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
💡 HANDLUNGSEMPFEHLUNG
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
🟢 KAUFGELEGENHEIT
|
||||||
|
→ Erwäge einen Einstieg mit kleiner Position
|
||||||
|
→ Warte ggf. auf Bestätigung durch weitere Signale
|
||||||
|
|
||||||
|
⚠️ Risiko-Hinweis: Diese Analyse hat eine Konfidenz von 72%
|
||||||
|
⚠️ Keine Anlageberatung - Trading auf eigenes Risiko!
|
||||||
|
|
||||||
|
╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Technische Details
|
||||||
|
|
||||||
|
### MACD-Parameter
|
||||||
|
- **Fast EMA**: 12 Perioden
|
||||||
|
- **Slow EMA**: 26 Perioden
|
||||||
|
- **Signal Line**: 9 Perioden
|
||||||
|
|
||||||
|
### Signal-Logik
|
||||||
|
- **Starker Kauf**: Bullish Crossover im negativen Bereich
|
||||||
|
- **Kauf**: Bullish Crossover oder positives Momentum
|
||||||
|
- **Halten**: Keine klare Richtung
|
||||||
|
- **Verkauf**: Bearish Crossover oder negatives Momentum
|
||||||
|
- **Starker Verkauf**: Bearish Crossover im positiven Bereich
|
||||||
|
|
||||||
|
### Gewichtung
|
||||||
|
- MACD (Technische Analyse): **60%**
|
||||||
|
- News-Sentiment: **40%**
|
||||||
|
- Bonus bei übereinstimmenden Signalen: **+10%**
|
||||||
|
|
||||||
|
## 📁 Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
bitcoin_trading/
|
||||||
|
├── __init__.py # Package Initialisierung
|
||||||
|
├── bitcoin_trader.py # Hauptprogramm (CLI)
|
||||||
|
├── data_fetcher.py # Bitcoin-Preisdaten-Abruf
|
||||||
|
├── macd_indicator.py # MACD-Indikator-Berechnung
|
||||||
|
├── news_sentiment.py # News-Sentiment-Analyse
|
||||||
|
├── signal_generator.py # Signal-Kombination & Empfehlung
|
||||||
|
├── requirements.txt # Python-Dependencies
|
||||||
|
└── README.md # Diese Datei
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔌 API-Quellen
|
||||||
|
|
||||||
|
### Preisdaten
|
||||||
|
- **Binance API** (primär) - Schnelle, zuverlässige Preisdaten
|
||||||
|
- **CoinGecko API** (fallback) - Backup-Datenquelle
|
||||||
|
|
||||||
|
### News
|
||||||
|
- **CryptoCompare News API** (kostenlos) - Crypto-spezifische News
|
||||||
|
- **NewsAPI** (optional) - Erweiterte News-Abdeckung
|
||||||
|
|
||||||
|
## ⚠️ Wichtige Hinweise
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
- **Dies ist KEINE Anlageberatung**
|
||||||
|
- Trading mit Kryptowährungen ist hochriskant
|
||||||
|
- Vergangene Performance garantiert keine zukünftigen Ergebnisse
|
||||||
|
- Investiere nur Geld, das du dir leisten kannst zu verlieren
|
||||||
|
- Führe deine eigene Due Diligence durch
|
||||||
|
|
||||||
|
### Risiken
|
||||||
|
- Marktvolatilität kann Signale schnell ungültig machen
|
||||||
|
- Technische Indikatoren sind nicht 100% zuverlässig
|
||||||
|
- News-Sentiment kann manipuliert sein
|
||||||
|
- API-Ausfälle können Daten beeinträchtigen
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- Verwende Signale als einen von mehreren Faktoren
|
||||||
|
- Setze immer Stop-Loss-Orders
|
||||||
|
- Diversifiziere dein Portfolio
|
||||||
|
- Handel nur mit klarem Kopf
|
||||||
|
- Dokumentiere deine Trades
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Fehler: "Konnte Bitcoin-Preis nicht abrufen"
|
||||||
|
- Überprüfe Internetverbindung
|
||||||
|
- APIs könnten temporär down sein
|
||||||
|
- Warte kurz und versuche es erneut
|
||||||
|
|
||||||
|
### Fehler: "Nicht genug Daten für MACD"
|
||||||
|
- Erhöhe `--days` Parameter (mindestens 30 Tage empfohlen)
|
||||||
|
- Stelle sicher, dass historische Daten geladen werden
|
||||||
|
|
||||||
|
### Sentiment zeigt immer "NEUTRAL"
|
||||||
|
- Möglicherweise keine aktuellen News verfügbar
|
||||||
|
- Verwende `--newsapi-key` für mehr News-Quellen
|
||||||
|
- News-APIs könnten Rate-Limits haben
|
||||||
|
|
||||||
|
## 🔄 Updates & Erweiterungen
|
||||||
|
|
||||||
|
### Geplante Features
|
||||||
|
- [ ] RSI (Relative Strength Index) Integration
|
||||||
|
- [ ] Bollinger Bands Analyse
|
||||||
|
- [ ] Machine Learning Modelle
|
||||||
|
- [ ] Email/Telegram Benachrichtigungen
|
||||||
|
- [ ] Backtesting-Funktionalität
|
||||||
|
- [ ] WebSocket Real-time Updates
|
||||||
|
- [ ] Multi-Coin Support (ETH, etc.)
|
||||||
|
|
||||||
|
### Erweiterungsmöglichkeiten
|
||||||
|
- Integration weiterer technischer Indikatoren
|
||||||
|
- Social Media Sentiment (Twitter/Reddit)
|
||||||
|
- On-Chain-Metriken (Wallet-Bewegungen)
|
||||||
|
- Advanced ML/AI Modelle
|
||||||
|
- Portfolio-Management-Features
|
||||||
|
|
||||||
|
## 📝 Lizenz
|
||||||
|
|
||||||
|
Dieses Projekt ist Teil des GetYourBand-Projekts.
|
||||||
|
|
||||||
|
## 🤝 Beitragen
|
||||||
|
|
||||||
|
Contributions sind willkommen! Bitte öffne ein Issue oder Pull Request.
|
||||||
|
|
||||||
|
## 📧 Support
|
||||||
|
|
||||||
|
Bei Fragen oder Problemen erstelle ein Issue im Repository.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with 📊 and ₿ for informed trading decisions**
|
||||||
|
|
||||||
|
⚠️ **Remember: Don't invest more than you can afford to lose!**
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Bitcoin Trading Signal System
|
||||||
|
Ein System zur Generierung von Kauf-/Verkaufsempfehlungen für Bitcoin
|
||||||
|
basierend auf MACD-Indikatoren und News-Sentiment-Analyse
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Bitcoin Trading Signal System"
|
||||||
|
|
||||||
|
from .data_fetcher import BitcoinDataFetcher
|
||||||
|
from .macd_indicator import MACDIndicator, MACDSignal
|
||||||
|
from .news_sentiment import NewsSentimentAnalyzer, SentimentScore
|
||||||
|
from .signal_generator import SignalGenerator, TradingAction, TradingSignal
|
||||||
|
from .bitcoin_trader import BitcoinTrader
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BitcoinDataFetcher',
|
||||||
|
'MACDIndicator',
|
||||||
|
'MACDSignal',
|
||||||
|
'NewsSentimentAnalyzer',
|
||||||
|
'SentimentScore',
|
||||||
|
'SignalGenerator',
|
||||||
|
'TradingAction',
|
||||||
|
'TradingSignal',
|
||||||
|
'BitcoinTrader',
|
||||||
|
]
|
||||||
Executable
+212
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Bitcoin Trading Signal System
|
||||||
|
Hauptprogramm für Bitcoin Trading-Empfehlungen basierend auf MACD und News-Sentiment
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from data_fetcher import BitcoinDataFetcher
|
||||||
|
from signal_generator import SignalGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class BitcoinTrader:
|
||||||
|
"""
|
||||||
|
Haupt-Klasse für das Bitcoin Trading Signal System
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, newsapi_key: Optional[str] = None, verbose: bool = False):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
newsapi_key: Optional NewsAPI-Schlüssel
|
||||||
|
verbose: Ausführliche Ausgabe
|
||||||
|
"""
|
||||||
|
self.data_fetcher = BitcoinDataFetcher()
|
||||||
|
self.signal_generator = SignalGenerator(newsapi_key=newsapi_key)
|
||||||
|
self.verbose = verbose
|
||||||
|
|
||||||
|
def run_analysis(self, days: int = 30) -> None:
|
||||||
|
"""
|
||||||
|
Führt komplette Trading-Analyse durch
|
||||||
|
|
||||||
|
Args:
|
||||||
|
days: Anzahl Tage für historische Daten
|
||||||
|
"""
|
||||||
|
print("╔══════════════════════════════════════════════════════════════════╗")
|
||||||
|
print("║ BITCOIN TRADING SIGNAL SYSTEM v1.0 ║")
|
||||||
|
print("╚══════════════════════════════════════════════════════════════════╝")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 1. Lade aktuelle Preisdaten
|
||||||
|
if self.verbose:
|
||||||
|
print("📊 Lade aktuelle Bitcoin-Preisdaten...")
|
||||||
|
|
||||||
|
current_price = self.data_fetcher.get_current_price()
|
||||||
|
|
||||||
|
if not current_price:
|
||||||
|
print("❌ Fehler: Konnte aktuellen Bitcoin-Preis nicht abrufen!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(f"✓ Aktueller BTC-Preis: ${current_price:,.2f}")
|
||||||
|
|
||||||
|
# 2. Lade historische Daten
|
||||||
|
if self.verbose:
|
||||||
|
print(f"📈 Lade historische Daten ({days} Tage)...")
|
||||||
|
|
||||||
|
price_df = self.data_fetcher.get_historical_data(days=days)
|
||||||
|
|
||||||
|
if price_df.empty:
|
||||||
|
print("❌ Fehler: Konnte historische Daten nicht abrufen!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(f"✓ {len(price_df)} Datenpunkte geladen")
|
||||||
|
|
||||||
|
# 3. Lade Marktdaten (optional)
|
||||||
|
if self.verbose:
|
||||||
|
print("💹 Lade erweiterte Marktdaten...")
|
||||||
|
|
||||||
|
market_data = self.data_fetcher.get_market_data()
|
||||||
|
|
||||||
|
if market_data and self.verbose:
|
||||||
|
print(f"✓ Marktdaten geladen")
|
||||||
|
if market_data.get('price_change_24h'):
|
||||||
|
change_24h = market_data['price_change_24h']
|
||||||
|
emoji = "📈" if change_24h > 0 else "📉"
|
||||||
|
print(f" {emoji} 24h Veränderung: {change_24h:+.2f}%")
|
||||||
|
|
||||||
|
# 4. Generiere Trading-Signal
|
||||||
|
if self.verbose:
|
||||||
|
print("\n🔍 Analysiere MACD-Indikatoren...")
|
||||||
|
print("📰 Analysiere News-Sentiment...")
|
||||||
|
print("🎯 Generiere Trading-Signal...\n")
|
||||||
|
|
||||||
|
signal = self.signal_generator.generate_signal(price_df, current_price)
|
||||||
|
|
||||||
|
# 5. Zeige Empfehlung
|
||||||
|
recommendation = self.signal_generator.get_recommendation_text(signal)
|
||||||
|
print(recommendation)
|
||||||
|
|
||||||
|
# 6. Zusätzliche Marktinformationen
|
||||||
|
if market_data and self.verbose:
|
||||||
|
print("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||||
|
print("📊 ZUSÄTZLICHE MARKTINFORMATIONEN")
|
||||||
|
print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
|
||||||
|
|
||||||
|
if market_data.get('market_cap'):
|
||||||
|
print(f" Marktkapitalisierung: ${market_data['market_cap']:,.0f}")
|
||||||
|
|
||||||
|
if market_data.get('total_volume'):
|
||||||
|
print(f" 24h Handelsvolumen: ${market_data['total_volume']:,.0f}")
|
||||||
|
|
||||||
|
if market_data.get('high_24h') and market_data.get('low_24h'):
|
||||||
|
print(f" 24h Hoch: ${market_data['high_24h']:,.2f}")
|
||||||
|
print(f" 24h Tief: ${market_data['low_24h']:,.2f}")
|
||||||
|
|
||||||
|
if market_data.get('price_change_7d'):
|
||||||
|
print(f" 7-Tage Veränderung: {market_data['price_change_7d']:+.2f}%")
|
||||||
|
|
||||||
|
if market_data.get('price_change_30d'):
|
||||||
|
print(f" 30-Tage Veränderung: {market_data['price_change_30d']:+.2f}%")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def get_quick_signal(self) -> str:
|
||||||
|
"""
|
||||||
|
Gibt schnelles Trading-Signal zurück (nur Empfehlung)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Signal-String
|
||||||
|
"""
|
||||||
|
current_price = self.data_fetcher.get_current_price()
|
||||||
|
if not current_price:
|
||||||
|
return "❌ Fehler beim Abrufen der Daten"
|
||||||
|
|
||||||
|
price_df = self.data_fetcher.get_historical_data(days=30)
|
||||||
|
if price_df.empty:
|
||||||
|
return "❌ Fehler beim Abrufen der Daten"
|
||||||
|
|
||||||
|
signal = self.signal_generator.generate_signal(price_df, current_price)
|
||||||
|
|
||||||
|
return f"{signal.action.value} (Konfidenz: {signal.confidence}%) @ ${signal.price:,.2f}"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Bitcoin Trading Signal System - MACD + News Sentiment Analyse',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Beispiele:
|
||||||
|
%(prog)s # Standard-Analyse
|
||||||
|
%(prog)s --verbose # Ausführliche Ausgabe
|
||||||
|
%(prog)s --days 60 # 60 Tage historische Daten
|
||||||
|
%(prog)s --quick # Schnelles Signal
|
||||||
|
%(prog)s --newsapi-key YOUR_KEY # Mit NewsAPI-Schlüssel
|
||||||
|
|
||||||
|
Hinweis:
|
||||||
|
- NewsAPI-Schlüssel optional (erhöht News-Quellen)
|
||||||
|
- Kostenlos bei https://newsapi.org
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-v', '--verbose',
|
||||||
|
action='store_true',
|
||||||
|
help='Ausführliche Ausgabe mit Ladestatus'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--days',
|
||||||
|
type=int,
|
||||||
|
default=30,
|
||||||
|
help='Anzahl Tage für historische Daten (Standard: 30)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-q', '--quick',
|
||||||
|
action='store_true',
|
||||||
|
help='Schnelles Signal ohne Details'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--newsapi-key',
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help='NewsAPI-Schlüssel für erweiterte News-Analyse'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Initialisiere Trader
|
||||||
|
trader = BitcoinTrader(
|
||||||
|
newsapi_key=args.newsapi_key,
|
||||||
|
verbose=args.verbose
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.quick:
|
||||||
|
# Schnelles Signal
|
||||||
|
signal = trader.get_quick_signal()
|
||||||
|
print(signal)
|
||||||
|
else:
|
||||||
|
# Vollständige Analyse
|
||||||
|
trader.run_analysis(days=args.days)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n⚠️ Analyse abgebrochen durch Benutzer")
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ Fehler: {e}")
|
||||||
|
if args.verbose:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
"""
|
||||||
|
Bitcoin Price Data Fetcher
|
||||||
|
Ruft aktuelle und historische Bitcoin-Preisdaten von verschiedenen APIs ab
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import pandas as pd
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class BitcoinDataFetcher:
|
||||||
|
"""Fetches Bitcoin price data from various sources"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.base_url_coingecko = "https://api.coingecko.com/api/v3"
|
||||||
|
self.base_url_binance = "https://api.binance.com/api/v3"
|
||||||
|
|
||||||
|
def get_current_price(self) -> Optional[float]:
|
||||||
|
"""
|
||||||
|
Holt den aktuellen Bitcoin-Preis in USD
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: Aktueller BTC/USD Preis oder None bei Fehler
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Versuche zuerst Binance (schneller und zuverlässiger)
|
||||||
|
url = f"{self.base_url_binance}/ticker/price"
|
||||||
|
params = {"symbol": "BTCUSDT"}
|
||||||
|
response = requests.get(url, params=params, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return float(data['price'])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Abrufen von Binance: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Fallback zu CoinGecko
|
||||||
|
url = f"{self.base_url_coingecko}/simple/price"
|
||||||
|
params = {
|
||||||
|
"ids": "bitcoin",
|
||||||
|
"vs_currencies": "usd"
|
||||||
|
}
|
||||||
|
response = requests.get(url, params=params, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return float(data['bitcoin']['usd'])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Abrufen von CoinGecko: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_historical_data(self, days: int = 30) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Holt historische Bitcoin-Preisdaten
|
||||||
|
|
||||||
|
Args:
|
||||||
|
days: Anzahl der Tage zurück
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame mit Spalten: timestamp, price, volume
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Binance Klines (Candlestick-Daten)
|
||||||
|
url = f"{self.base_url_binance}/klines"
|
||||||
|
|
||||||
|
# Berechne Zeitstempel
|
||||||
|
end_time = int(datetime.now().timestamp() * 1000)
|
||||||
|
start_time = int((datetime.now() - timedelta(days=days)).timestamp() * 1000)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"symbol": "BTCUSDT",
|
||||||
|
"interval": "1h", # Stündliche Daten
|
||||||
|
"startTime": start_time,
|
||||||
|
"endTime": end_time,
|
||||||
|
"limit": 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params, timeout=30)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Konvertiere zu DataFrame
|
||||||
|
df = pd.DataFrame(data, columns=[
|
||||||
|
'timestamp', 'open', 'high', 'low', 'close',
|
||||||
|
'volume', 'close_time', 'quote_volume', 'trades',
|
||||||
|
'taker_buy_base', 'taker_buy_quote', 'ignore'
|
||||||
|
])
|
||||||
|
|
||||||
|
# Behalte nur relevante Spalten
|
||||||
|
df = df[['timestamp', 'close', 'volume']]
|
||||||
|
df.columns = ['timestamp', 'price', 'volume']
|
||||||
|
|
||||||
|
# Konvertiere Datentypen
|
||||||
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||||
|
df['price'] = df['price'].astype(float)
|
||||||
|
df['volume'] = df['volume'].astype(float)
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Abrufen historischer Daten: {e}")
|
||||||
|
|
||||||
|
# Fallback: Leeres DataFrame
|
||||||
|
return pd.DataFrame(columns=['timestamp', 'price', 'volume'])
|
||||||
|
|
||||||
|
def get_market_data(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Holt erweiterte Marktdaten für Bitcoin
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict mit Marktdaten (Volumen, Marktkapitalisierung, etc.)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url_coingecko}/coins/bitcoin"
|
||||||
|
params = {
|
||||||
|
"localization": "false",
|
||||||
|
"tickers": "false",
|
||||||
|
"market_data": "true",
|
||||||
|
"community_data": "false",
|
||||||
|
"developer_data": "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params, timeout=15)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
market_data = data.get('market_data', {})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'current_price': market_data.get('current_price', {}).get('usd'),
|
||||||
|
'market_cap': market_data.get('market_cap', {}).get('usd'),
|
||||||
|
'total_volume': market_data.get('total_volume', {}).get('usd'),
|
||||||
|
'price_change_24h': market_data.get('price_change_percentage_24h'),
|
||||||
|
'price_change_7d': market_data.get('price_change_percentage_7d'),
|
||||||
|
'price_change_30d': market_data.get('price_change_percentage_30d'),
|
||||||
|
'high_24h': market_data.get('high_24h', {}).get('usd'),
|
||||||
|
'low_24h': market_data.get('low_24h', {}).get('usd'),
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Abrufen von Marktdaten: {e}")
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test
|
||||||
|
fetcher = BitcoinDataFetcher()
|
||||||
|
|
||||||
|
print("=== Bitcoin Preis ===")
|
||||||
|
price = fetcher.get_current_price()
|
||||||
|
if price:
|
||||||
|
print(f"Aktueller BTC/USD Preis: ${price:,.2f}")
|
||||||
|
|
||||||
|
print("\n=== Marktdaten ===")
|
||||||
|
market = fetcher.get_market_data()
|
||||||
|
for key, value in market.items():
|
||||||
|
if value is not None:
|
||||||
|
if 'price' in key or 'cap' in key or 'volume' in key or 'high' in key or 'low' in key:
|
||||||
|
print(f"{key}: ${value:,.2f}")
|
||||||
|
else:
|
||||||
|
print(f"{key}: {value:.2f}%")
|
||||||
|
|
||||||
|
print("\n=== Historische Daten (letzte 7 Tage) ===")
|
||||||
|
df = fetcher.get_historical_data(days=7)
|
||||||
|
if not df.empty:
|
||||||
|
print(f"Anzahl Datenpunkte: {len(df)}")
|
||||||
|
print(df.tail())
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
"""
|
||||||
|
MACD (Moving Average Convergence Divergence) Indicator
|
||||||
|
Berechnet MACD-Signale für Bitcoin Trading
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from typing import Dict, Tuple, Optional
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class MACDSignal(Enum):
|
||||||
|
"""MACD Signal-Typen"""
|
||||||
|
STRONG_BUY = "STARKER KAUF"
|
||||||
|
BUY = "KAUF"
|
||||||
|
NEUTRAL = "NEUTRAL"
|
||||||
|
SELL = "VERKAUF"
|
||||||
|
STRONG_SELL = "STARKER VERKAUF"
|
||||||
|
|
||||||
|
|
||||||
|
class MACDIndicator:
|
||||||
|
"""
|
||||||
|
MACD-Indikator für technische Analyse
|
||||||
|
|
||||||
|
Standard-Parameter:
|
||||||
|
- Fast EMA: 12 Perioden
|
||||||
|
- Slow EMA: 26 Perioden
|
||||||
|
- Signal: 9 Perioden
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
fast_period: Schnelle EMA-Periode (Standard: 12)
|
||||||
|
slow_period: Langsame EMA-Periode (Standard: 26)
|
||||||
|
signal_period: Signal-Linie-Periode (Standard: 9)
|
||||||
|
"""
|
||||||
|
self.fast_period = fast_period
|
||||||
|
self.slow_period = slow_period
|
||||||
|
self.signal_period = signal_period
|
||||||
|
|
||||||
|
def calculate_ema(self, data: pd.Series, period: int) -> pd.Series:
|
||||||
|
"""
|
||||||
|
Berechnet Exponential Moving Average (EMA)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Preisdaten
|
||||||
|
period: EMA-Periode
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EMA-Serie
|
||||||
|
"""
|
||||||
|
return data.ewm(span=period, adjust=False).mean()
|
||||||
|
|
||||||
|
def calculate_macd(self, prices: pd.Series) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Berechnet MACD-Indikator
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prices: Preis-Serie (normalerweise Close-Preise)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrame mit MACD, Signal und Histogram
|
||||||
|
"""
|
||||||
|
# Berechne EMAs
|
||||||
|
ema_fast = self.calculate_ema(prices, self.fast_period)
|
||||||
|
ema_slow = self.calculate_ema(prices, self.slow_period)
|
||||||
|
|
||||||
|
# MACD-Linie
|
||||||
|
macd_line = ema_fast - ema_slow
|
||||||
|
|
||||||
|
# Signal-Linie (9-Tage EMA der MACD-Linie)
|
||||||
|
signal_line = self.calculate_ema(macd_line, self.signal_period)
|
||||||
|
|
||||||
|
# MACD-Histogramm (Differenz zwischen MACD und Signal)
|
||||||
|
histogram = macd_line - signal_line
|
||||||
|
|
||||||
|
# Erstelle DataFrame
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'macd': macd_line,
|
||||||
|
'signal': signal_line,
|
||||||
|
'histogram': histogram
|
||||||
|
})
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def get_signal(self, df: pd.DataFrame) -> Tuple[MACDSignal, Dict]:
|
||||||
|
"""
|
||||||
|
Generiert Trading-Signal basierend auf MACD
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame mit price-Spalte
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple aus (Signal, Details-Dict)
|
||||||
|
"""
|
||||||
|
if df.empty or len(df) < self.slow_period + self.signal_period:
|
||||||
|
return MACDSignal.NEUTRAL, {
|
||||||
|
'reason': 'Nicht genug Daten für MACD-Berechnung',
|
||||||
|
'confidence': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Berechne MACD
|
||||||
|
macd_df = self.calculate_macd(df['price'])
|
||||||
|
|
||||||
|
# Hole aktuelle und vorherige Werte
|
||||||
|
current_macd = macd_df['macd'].iloc[-1]
|
||||||
|
current_signal = macd_df['signal'].iloc[-1]
|
||||||
|
current_histogram = macd_df['histogram'].iloc[-1]
|
||||||
|
|
||||||
|
prev_macd = macd_df['macd'].iloc[-2]
|
||||||
|
prev_signal = macd_df['signal'].iloc[-2]
|
||||||
|
prev_histogram = macd_df['histogram'].iloc[-2]
|
||||||
|
|
||||||
|
# Bestimme Signal-Typ
|
||||||
|
signal = MACDSignal.NEUTRAL
|
||||||
|
confidence = 0
|
||||||
|
reasons = []
|
||||||
|
|
||||||
|
# 1. Bullish Crossover (MACD kreuzt Signal von unten)
|
||||||
|
if prev_macd <= prev_signal and current_macd > current_signal:
|
||||||
|
signal = MACDSignal.BUY
|
||||||
|
confidence = 70
|
||||||
|
reasons.append("Bullish Crossover: MACD kreuzt Signal-Linie von unten")
|
||||||
|
|
||||||
|
# Starkes Kaufsignal, wenn zusätzlich im negativen Bereich
|
||||||
|
if current_macd < 0:
|
||||||
|
signal = MACDSignal.STRONG_BUY
|
||||||
|
confidence = 85
|
||||||
|
reasons.append("MACD im negativen Bereich → Überkauft")
|
||||||
|
|
||||||
|
# 2. Bearish Crossover (MACD kreuzt Signal von oben)
|
||||||
|
elif prev_macd >= prev_signal and current_macd < current_signal:
|
||||||
|
signal = MACDSignal.SELL
|
||||||
|
confidence = 70
|
||||||
|
reasons.append("Bearish Crossover: MACD kreuzt Signal-Linie von oben")
|
||||||
|
|
||||||
|
# Starkes Verkaufssignal, wenn zusätzlich im positiven Bereich
|
||||||
|
if current_macd > 0:
|
||||||
|
signal = MACDSignal.STRONG_SELL
|
||||||
|
confidence = 85
|
||||||
|
reasons.append("MACD im positiven Bereich → Überverkauft")
|
||||||
|
|
||||||
|
# 3. Divergenz-Analyse (Histogramm)
|
||||||
|
else:
|
||||||
|
histogram_trend = current_histogram - prev_histogram
|
||||||
|
|
||||||
|
if histogram_trend > 0 and current_histogram > 0:
|
||||||
|
signal = MACDSignal.BUY
|
||||||
|
confidence = 55
|
||||||
|
reasons.append("Positives Momentum: Histogramm steigt")
|
||||||
|
|
||||||
|
elif histogram_trend < 0 and current_histogram < 0:
|
||||||
|
signal = MACDSignal.SELL
|
||||||
|
confidence = 55
|
||||||
|
reasons.append("Negatives Momentum: Histogramm fällt")
|
||||||
|
|
||||||
|
else:
|
||||||
|
signal = MACDSignal.NEUTRAL
|
||||||
|
confidence = 30
|
||||||
|
reasons.append("Kein klarer Trend erkennbar")
|
||||||
|
|
||||||
|
# Zusätzliche Analyse: Preis-Trend
|
||||||
|
recent_prices = df['price'].tail(10)
|
||||||
|
price_change = ((recent_prices.iloc[-1] - recent_prices.iloc[0]) / recent_prices.iloc[0]) * 100
|
||||||
|
|
||||||
|
if abs(price_change) > 5:
|
||||||
|
if price_change > 0:
|
||||||
|
reasons.append(f"Preis-Trend: +{price_change:.2f}% (aufwärts)")
|
||||||
|
else:
|
||||||
|
reasons.append(f"Preis-Trend: {price_change:.2f}% (abwärts)")
|
||||||
|
|
||||||
|
details = {
|
||||||
|
'macd': float(current_macd),
|
||||||
|
'signal': float(current_signal),
|
||||||
|
'histogram': float(current_histogram),
|
||||||
|
'prev_histogram': float(prev_histogram),
|
||||||
|
'crossover': current_macd > current_signal,
|
||||||
|
'confidence': confidence,
|
||||||
|
'reasons': reasons,
|
||||||
|
'price_change_10d': float(price_change)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signal, details
|
||||||
|
|
||||||
|
def analyze_trend(self, df: pd.DataFrame, periods: int = 20) -> str:
|
||||||
|
"""
|
||||||
|
Analysiert den Trend der letzten Perioden
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame mit MACD-Daten
|
||||||
|
periods: Anzahl Perioden zur Analyse
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Trend-Beschreibung
|
||||||
|
"""
|
||||||
|
macd_df = self.calculate_macd(df['price'])
|
||||||
|
|
||||||
|
if len(macd_df) < periods:
|
||||||
|
return "Nicht genug Daten"
|
||||||
|
|
||||||
|
recent_histogram = macd_df['histogram'].tail(periods)
|
||||||
|
|
||||||
|
# Zähle positive/negative Balken
|
||||||
|
positive_bars = (recent_histogram > 0).sum()
|
||||||
|
negative_bars = (recent_histogram < 0).sum()
|
||||||
|
|
||||||
|
if positive_bars > negative_bars * 1.5:
|
||||||
|
return "Starker Aufwärtstrend"
|
||||||
|
elif positive_bars > negative_bars:
|
||||||
|
return "Leichter Aufwärtstrend"
|
||||||
|
elif negative_bars > positive_bars * 1.5:
|
||||||
|
return "Starker Abwärtstrend"
|
||||||
|
elif negative_bars > positive_bars:
|
||||||
|
return "Leichter Abwärtstrend"
|
||||||
|
else:
|
||||||
|
return "Seitwärtstrend"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test mit simulierten Daten
|
||||||
|
print("=== MACD Indicator Test ===\n")
|
||||||
|
|
||||||
|
# Erstelle Test-Daten
|
||||||
|
dates = pd.date_range(start='2024-01-01', periods=100, freq='D')
|
||||||
|
prices = 40000 + np.cumsum(np.random.randn(100) * 500) # Random Walk
|
||||||
|
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'timestamp': dates,
|
||||||
|
'price': prices
|
||||||
|
})
|
||||||
|
|
||||||
|
# Initialisiere MACD
|
||||||
|
macd = MACDIndicator()
|
||||||
|
|
||||||
|
# Berechne MACD
|
||||||
|
macd_df = macd.calculate_macd(df['price'])
|
||||||
|
|
||||||
|
print("Letzte 5 MACD-Werte:")
|
||||||
|
print(macd_df.tail())
|
||||||
|
|
||||||
|
print("\n=== Trading Signal ===")
|
||||||
|
signal, details = macd.get_signal(df)
|
||||||
|
|
||||||
|
print(f"Signal: {signal.value}")
|
||||||
|
print(f"Konfidenz: {details['confidence']}%")
|
||||||
|
print(f"\nMACD: {details['macd']:.2f}")
|
||||||
|
print(f"Signal: {details['signal']:.2f}")
|
||||||
|
print(f"Histogram: {details['histogram']:.2f}")
|
||||||
|
print(f"\nGründe:")
|
||||||
|
for reason in details['reasons']:
|
||||||
|
print(f" - {reason}")
|
||||||
|
|
||||||
|
print(f"\nTrend-Analyse: {macd.analyze_trend(df)}")
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
"""
|
||||||
|
News Sentiment Analyzer für Bitcoin
|
||||||
|
Analysiert Nachrichten und bestimmt das Markt-Sentiment
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import List, Dict, Optional, Tuple
|
||||||
|
from enum import Enum
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class SentimentScore(Enum):
|
||||||
|
"""Sentiment-Score-Typen"""
|
||||||
|
VERY_POSITIVE = "SEHR POSITIV"
|
||||||
|
POSITIVE = "POSITIV"
|
||||||
|
NEUTRAL = "NEUTRAL"
|
||||||
|
NEGATIVE = "NEGATIV"
|
||||||
|
VERY_NEGATIVE = "SEHR NEGATIV"
|
||||||
|
|
||||||
|
|
||||||
|
class NewsSentimentAnalyzer:
|
||||||
|
"""
|
||||||
|
Analysiert Bitcoin-bezogene Nachrichten und bestimmt das Sentiment
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, api_key: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
api_key: NewsAPI-Schlüssel (optional, verwendet Free Tier wenn None)
|
||||||
|
"""
|
||||||
|
self.api_key = api_key
|
||||||
|
self.newsapi_url = "https://newsapi.org/v2/everything"
|
||||||
|
|
||||||
|
# Sentiment-Keyword-Listen
|
||||||
|
self.positive_keywords = [
|
||||||
|
'bullish', 'surge', 'rally', 'gain', 'rise', 'soar', 'jump',
|
||||||
|
'breakthrough', 'adoption', 'institutional', 'buy', 'investment',
|
||||||
|
'growth', 'profit', 'support', 'upgrade', 'positive', 'optimistic',
|
||||||
|
'breakout', 'moon', 'accumulation', 'bull run', 'all-time high',
|
||||||
|
'ath', 'recovery', 'uptrend', 'momentum', 'strong'
|
||||||
|
]
|
||||||
|
|
||||||
|
self.negative_keywords = [
|
||||||
|
'bearish', 'crash', 'plunge', 'fall', 'drop', 'decline', 'sell',
|
||||||
|
'regulation', 'ban', 'warning', 'risk', 'fear', 'panic', 'loss',
|
||||||
|
'hack', 'fraud', 'scam', 'bubble', 'concern', 'volatile',
|
||||||
|
'downtrend', 'resistance', 'bear market', 'dump', 'correction',
|
||||||
|
'selling pressure', 'oversold', 'weak', 'uncertainty'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Crypto News Sources (kostenlos zugänglich)
|
||||||
|
self.crypto_news_sources = [
|
||||||
|
'https://cryptopanic.com/api/v1/posts/',
|
||||||
|
'https://min-api.cryptocompare.com/data/v2/news/',
|
||||||
|
]
|
||||||
|
|
||||||
|
def fetch_news_newsapi(self, days: int = 1) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Holt Bitcoin-News von NewsAPI
|
||||||
|
|
||||||
|
Args:
|
||||||
|
days: Anzahl Tage zurück
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste von News-Artikeln
|
||||||
|
"""
|
||||||
|
if not self.api_key:
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
from_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'q': 'bitcoin OR BTC OR cryptocurrency',
|
||||||
|
'from': from_date,
|
||||||
|
'sortBy': 'publishedAt',
|
||||||
|
'language': 'en',
|
||||||
|
'apiKey': self.api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(self.newsapi_url, params=params, timeout=15)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return data.get('articles', [])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Abrufen von NewsAPI: {e}")
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def fetch_news_cryptocompare(self, limit: int = 50) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Holt Bitcoin-News von CryptoCompare (kostenlos)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
limit: Anzahl News-Artikel
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste von News-Artikeln
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
url = "https://min-api.cryptocompare.com/data/v2/news/"
|
||||||
|
params = {
|
||||||
|
'categories': 'BTC',
|
||||||
|
'lang': 'EN'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params, timeout=15)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
news_list = data.get('Data', [])
|
||||||
|
|
||||||
|
# Formatiere zu einheitlichem Format
|
||||||
|
formatted_news = []
|
||||||
|
for item in news_list[:limit]:
|
||||||
|
formatted_news.append({
|
||||||
|
'title': item.get('title', ''),
|
||||||
|
'description': item.get('body', ''),
|
||||||
|
'publishedAt': datetime.fromtimestamp(item.get('published_on', 0)),
|
||||||
|
'source': item.get('source', ''),
|
||||||
|
'url': item.get('url', '')
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatted_news
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Abrufen von CryptoCompare: {e}")
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def analyze_text_sentiment(self, text: str) -> Tuple[float, Dict]:
|
||||||
|
"""
|
||||||
|
Analysiert Sentiment eines Textes
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Zu analysierender Text
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple aus (Score -1 bis +1, Details)
|
||||||
|
"""
|
||||||
|
if not text:
|
||||||
|
return 0.0, {'positive': 0, 'negative': 0}
|
||||||
|
|
||||||
|
text_lower = text.lower()
|
||||||
|
|
||||||
|
# Zähle positive und negative Keywords
|
||||||
|
positive_count = sum(1 for keyword in self.positive_keywords if keyword in text_lower)
|
||||||
|
negative_count = sum(1 for keyword in self.negative_keywords if keyword in text_lower)
|
||||||
|
|
||||||
|
# Berechne Score (-1 bis +1)
|
||||||
|
total_keywords = positive_count + negative_count
|
||||||
|
if total_keywords == 0:
|
||||||
|
score = 0.0
|
||||||
|
else:
|
||||||
|
score = (positive_count - negative_count) / total_keywords
|
||||||
|
|
||||||
|
details = {
|
||||||
|
'positive': positive_count,
|
||||||
|
'negative': negative_count,
|
||||||
|
'total': total_keywords
|
||||||
|
}
|
||||||
|
|
||||||
|
return score, details
|
||||||
|
|
||||||
|
def analyze_news_sentiment(self, days: int = 1, limit: int = 50) -> Tuple[SentimentScore, Dict]:
|
||||||
|
"""
|
||||||
|
Analysiert Gesamt-Sentiment der aktuellen Bitcoin-Nachrichten
|
||||||
|
|
||||||
|
Args:
|
||||||
|
days: Anzahl Tage zurück
|
||||||
|
limit: Max. Anzahl Artikel
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple aus (Sentiment, Details)
|
||||||
|
"""
|
||||||
|
# Hole News von verschiedenen Quellen
|
||||||
|
news_articles = []
|
||||||
|
|
||||||
|
# Versuche NewsAPI (falls API-Key vorhanden)
|
||||||
|
if self.api_key:
|
||||||
|
news_articles.extend(self.fetch_news_newsapi(days))
|
||||||
|
|
||||||
|
# Hole von CryptoCompare (kostenlos)
|
||||||
|
crypto_news = self.fetch_news_cryptocompare(limit)
|
||||||
|
news_articles.extend(crypto_news)
|
||||||
|
|
||||||
|
if not news_articles:
|
||||||
|
return SentimentScore.NEUTRAL, {
|
||||||
|
'reason': 'Keine News gefunden',
|
||||||
|
'confidence': 0,
|
||||||
|
'articles_analyzed': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Analysiere jeden Artikel
|
||||||
|
sentiment_scores = []
|
||||||
|
positive_articles = 0
|
||||||
|
negative_articles = 0
|
||||||
|
neutral_articles = 0
|
||||||
|
|
||||||
|
for article in news_articles[:limit]:
|
||||||
|
title = article.get('title', '')
|
||||||
|
description = article.get('description', '')
|
||||||
|
combined_text = f"{title} {description}"
|
||||||
|
|
||||||
|
score, details = self.analyze_text_sentiment(combined_text)
|
||||||
|
sentiment_scores.append(score)
|
||||||
|
|
||||||
|
if score > 0.2:
|
||||||
|
positive_articles += 1
|
||||||
|
elif score < -0.2:
|
||||||
|
negative_articles += 1
|
||||||
|
else:
|
||||||
|
neutral_articles += 1
|
||||||
|
|
||||||
|
# Berechne Durchschnitts-Sentiment
|
||||||
|
avg_sentiment = sum(sentiment_scores) / len(sentiment_scores) if sentiment_scores else 0
|
||||||
|
|
||||||
|
# Bestimme Gesamt-Sentiment
|
||||||
|
if avg_sentiment > 0.4:
|
||||||
|
sentiment = SentimentScore.VERY_POSITIVE
|
||||||
|
confidence = min(95, int(70 + abs(avg_sentiment) * 50))
|
||||||
|
elif avg_sentiment > 0.15:
|
||||||
|
sentiment = SentimentScore.POSITIVE
|
||||||
|
confidence = min(85, int(60 + abs(avg_sentiment) * 50))
|
||||||
|
elif avg_sentiment < -0.4:
|
||||||
|
sentiment = SentimentScore.VERY_NEGATIVE
|
||||||
|
confidence = min(95, int(70 + abs(avg_sentiment) * 50))
|
||||||
|
elif avg_sentiment < -0.15:
|
||||||
|
sentiment = SentimentScore.NEGATIVE
|
||||||
|
confidence = min(85, int(60 + abs(avg_sentiment) * 50))
|
||||||
|
else:
|
||||||
|
sentiment = SentimentScore.NEUTRAL
|
||||||
|
confidence = 50
|
||||||
|
|
||||||
|
details = {
|
||||||
|
'average_sentiment': float(avg_sentiment),
|
||||||
|
'confidence': confidence,
|
||||||
|
'articles_analyzed': len(news_articles),
|
||||||
|
'positive_articles': positive_articles,
|
||||||
|
'negative_articles': negative_articles,
|
||||||
|
'neutral_articles': neutral_articles,
|
||||||
|
'top_articles': news_articles[:5] # Top 5 für Details
|
||||||
|
}
|
||||||
|
|
||||||
|
return sentiment, details
|
||||||
|
|
||||||
|
def get_trending_topics(self, news_articles: List[Dict]) -> List[str]:
|
||||||
|
"""
|
||||||
|
Extrahiert Trend-Topics aus News
|
||||||
|
|
||||||
|
Args:
|
||||||
|
news_articles: Liste von News-Artikeln
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste der häufigsten Topics
|
||||||
|
"""
|
||||||
|
topics = []
|
||||||
|
keywords = [
|
||||||
|
'regulation', 'etf', 'institutional', 'mining',
|
||||||
|
'halving', 'adoption', 'sec', 'fed', 'inflation',
|
||||||
|
'blockchain', 'defi', 'nft', 'altcoin'
|
||||||
|
]
|
||||||
|
|
||||||
|
for article in news_articles:
|
||||||
|
text = f"{article.get('title', '')} {article.get('description', '')}".lower()
|
||||||
|
for keyword in keywords:
|
||||||
|
if keyword in text:
|
||||||
|
topics.append(keyword)
|
||||||
|
|
||||||
|
# Zähle Häufigkeit
|
||||||
|
from collections import Counter
|
||||||
|
topic_counts = Counter(topics)
|
||||||
|
return [topic for topic, count in topic_counts.most_common(5)]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test
|
||||||
|
print("=== Bitcoin News Sentiment Analyzer ===\n")
|
||||||
|
|
||||||
|
analyzer = NewsSentimentAnalyzer()
|
||||||
|
|
||||||
|
print("Lade Bitcoin-Nachrichten...")
|
||||||
|
sentiment, details = analyzer.analyze_news_sentiment(days=1, limit=30)
|
||||||
|
|
||||||
|
print(f"\n=== Sentiment-Analyse ===")
|
||||||
|
print(f"Gesamt-Sentiment: {sentiment.value}")
|
||||||
|
print(f"Konfidenz: {details['confidence']}%")
|
||||||
|
print(f"Durchschnitts-Score: {details['average_sentiment']:.3f}")
|
||||||
|
print(f"\nAnalysierte Artikel: {details['articles_analyzed']}")
|
||||||
|
print(f" Positiv: {details['positive_articles']}")
|
||||||
|
print(f" Neutral: {details['neutral_articles']}")
|
||||||
|
print(f" Negativ: {details['negative_articles']}")
|
||||||
|
|
||||||
|
if details.get('top_articles'):
|
||||||
|
print(f"\n=== Top 3 Schlagzeilen ===")
|
||||||
|
for i, article in enumerate(details['top_articles'][:3], 1):
|
||||||
|
print(f"{i}. {article.get('title', 'N/A')}")
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Bitcoin Trading Signal System - Python Dependencies
|
||||||
|
|
||||||
|
# Data Processing
|
||||||
|
pandas>=2.0.0
|
||||||
|
numpy>=1.24.0
|
||||||
|
|
||||||
|
# HTTP Requests
|
||||||
|
requests>=2.31.0
|
||||||
|
|
||||||
|
# Optional: Enhanced Sentiment Analysis
|
||||||
|
# textblob>=0.17.0
|
||||||
|
# vaderSentiment>=3.3.2
|
||||||
|
|
||||||
|
# Optional: Advanced Technical Analysis
|
||||||
|
# ta>=0.11.0
|
||||||
|
# pandas-ta>=0.3.14b0
|
||||||
|
|
||||||
|
# Development/Testing (optional)
|
||||||
|
# pytest>=7.4.0
|
||||||
|
# pytest-cov>=4.1.0
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
"""
|
||||||
|
Trading Signal Generator
|
||||||
|
Kombiniert MACD-Indikatoren und News-Sentiment für Trading-Empfehlungen
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Tuple
|
||||||
|
from enum import Enum
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from macd_indicator import MACDIndicator, MACDSignal
|
||||||
|
from news_sentiment import NewsSentimentAnalyzer, SentimentScore
|
||||||
|
|
||||||
|
|
||||||
|
class TradingAction(Enum):
|
||||||
|
"""Trading-Empfehlungs-Typen"""
|
||||||
|
STRONG_BUY = "🟢 STARKER KAUF"
|
||||||
|
BUY = "🟢 KAUF"
|
||||||
|
HOLD = "🟡 HALTEN"
|
||||||
|
SELL = "🔴 VERKAUF"
|
||||||
|
STRONG_SELL = "🔴 STARKER VERKAUF"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TradingSignal:
|
||||||
|
"""Datenklasse für Trading-Signale"""
|
||||||
|
action: TradingAction
|
||||||
|
confidence: int # 0-100
|
||||||
|
price: float
|
||||||
|
timestamp: datetime
|
||||||
|
macd_signal: MACDSignal
|
||||||
|
sentiment: SentimentScore
|
||||||
|
reasons: list
|
||||||
|
technical_details: dict
|
||||||
|
sentiment_details: dict
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"""
|
||||||
|
╔══════════════════════════════════════════════════════════════════╗
|
||||||
|
║ BITCOIN TRADING SIGNAL - {self.timestamp.strftime('%Y-%m-%d %H:%M')} ║
|
||||||
|
╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
📊 EMPFEHLUNG: {self.action.value}
|
||||||
|
💯 KONFIDENZ: {self.confidence}%
|
||||||
|
💰 AKTUELLER PREIS: ${self.price:,.2f}
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📈 TECHNISCHE ANALYSE (MACD)
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Signal: {self.macd_signal.value}
|
||||||
|
MACD: {self.technical_details.get('macd', 0):.2f}
|
||||||
|
Signal Line: {self.technical_details.get('signal', 0):.2f}
|
||||||
|
Histogram: {self.technical_details.get('histogram', 0):.2f}
|
||||||
|
Preis-Änderung (10 Tage): {self.technical_details.get('price_change_10d', 0):.2f}%
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📰 SENTIMENT-ANALYSE
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
Markt-Sentiment: {self.sentiment.value}
|
||||||
|
Sentiment-Score: {self.sentiment_details.get('average_sentiment', 0):.3f}
|
||||||
|
Analysierte Artikel: {self.sentiment_details.get('articles_analyzed', 0)}
|
||||||
|
├─ Positiv: {self.sentiment_details.get('positive_articles', 0)}
|
||||||
|
├─ Neutral: {self.sentiment_details.get('neutral_articles', 0)}
|
||||||
|
└─ Negativ: {self.sentiment_details.get('negative_articles', 0)}
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
📋 BEGRÜNDUNG
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
"""
|
||||||
|
|
||||||
|
def format_reasons(self):
|
||||||
|
"""Formatiert die Gründe"""
|
||||||
|
output = ""
|
||||||
|
for i, reason in enumerate(self.reasons, 1):
|
||||||
|
output += f" {i}. {reason}\n"
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
class SignalGenerator:
|
||||||
|
"""
|
||||||
|
Generiert Trading-Signale durch Kombination von MACD und Sentiment
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, newsapi_key: str = None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
newsapi_key: Optional NewsAPI-Schlüssel für erweiterte News
|
||||||
|
"""
|
||||||
|
self.macd_indicator = MACDIndicator()
|
||||||
|
self.sentiment_analyzer = NewsSentimentAnalyzer(api_key=newsapi_key)
|
||||||
|
|
||||||
|
# Gewichtungen für Signal-Berechnung
|
||||||
|
self.macd_weight = 0.6 # 60% Gewichtung für technische Analyse
|
||||||
|
self.sentiment_weight = 0.4 # 40% Gewichtung für Sentiment
|
||||||
|
|
||||||
|
def calculate_combined_score(self, macd_signal: MACDSignal,
|
||||||
|
sentiment: SentimentScore,
|
||||||
|
macd_confidence: int,
|
||||||
|
sentiment_confidence: int) -> Tuple[float, int]:
|
||||||
|
"""
|
||||||
|
Berechnet kombinierten Score aus MACD und Sentiment
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple aus (Score -1 bis +1, Gesamt-Konfidenz)
|
||||||
|
"""
|
||||||
|
# Konvertiere Signale zu Scores (-1 bis +1)
|
||||||
|
macd_score_map = {
|
||||||
|
MACDSignal.STRONG_BUY: 1.0,
|
||||||
|
MACDSignal.BUY: 0.5,
|
||||||
|
MACDSignal.NEUTRAL: 0.0,
|
||||||
|
MACDSignal.SELL: -0.5,
|
||||||
|
MACDSignal.STRONG_SELL: -1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
sentiment_score_map = {
|
||||||
|
SentimentScore.VERY_POSITIVE: 1.0,
|
||||||
|
SentimentScore.POSITIVE: 0.5,
|
||||||
|
SentimentScore.NEUTRAL: 0.0,
|
||||||
|
SentimentScore.NEGATIVE: -0.5,
|
||||||
|
SentimentScore.VERY_NEGATIVE: -1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
macd_score = macd_score_map.get(macd_signal, 0)
|
||||||
|
sentiment_score = sentiment_score_map.get(sentiment, 0)
|
||||||
|
|
||||||
|
# Gewichteter kombinierter Score
|
||||||
|
combined_score = (macd_score * self.macd_weight +
|
||||||
|
sentiment_score * self.sentiment_weight)
|
||||||
|
|
||||||
|
# Gewichtete kombinierte Konfidenz
|
||||||
|
combined_confidence = int(
|
||||||
|
macd_confidence * self.macd_weight +
|
||||||
|
sentiment_confidence * self.sentiment_weight
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bonus für übereinstimmende Signale
|
||||||
|
if (macd_score > 0 and sentiment_score > 0) or \
|
||||||
|
(macd_score < 0 and sentiment_score < 0):
|
||||||
|
combined_confidence = min(100, combined_confidence + 10)
|
||||||
|
|
||||||
|
return combined_score, combined_confidence
|
||||||
|
|
||||||
|
def generate_signal(self, price_df, current_price: float) -> TradingSignal:
|
||||||
|
"""
|
||||||
|
Generiert Trading-Signal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
price_df: DataFrame mit historischen Preisen
|
||||||
|
current_price: Aktueller Bitcoin-Preis
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TradingSignal mit Empfehlung
|
||||||
|
"""
|
||||||
|
# Hole MACD-Signal
|
||||||
|
macd_signal, macd_details = self.macd_indicator.get_signal(price_df)
|
||||||
|
|
||||||
|
# Hole Sentiment
|
||||||
|
sentiment, sentiment_details = self.sentiment_analyzer.analyze_news_sentiment(
|
||||||
|
days=1, limit=30
|
||||||
|
)
|
||||||
|
|
||||||
|
# Berechne kombinierten Score
|
||||||
|
combined_score, confidence = self.calculate_combined_score(
|
||||||
|
macd_signal,
|
||||||
|
sentiment,
|
||||||
|
macd_details['confidence'],
|
||||||
|
sentiment_details['confidence']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bestimme Trading-Action
|
||||||
|
if combined_score >= 0.6:
|
||||||
|
action = TradingAction.STRONG_BUY
|
||||||
|
elif combined_score >= 0.2:
|
||||||
|
action = TradingAction.BUY
|
||||||
|
elif combined_score <= -0.6:
|
||||||
|
action = TradingAction.STRONG_SELL
|
||||||
|
elif combined_score <= -0.2:
|
||||||
|
action = TradingAction.SELL
|
||||||
|
else:
|
||||||
|
action = TradingAction.HOLD
|
||||||
|
|
||||||
|
# Sammle Gründe
|
||||||
|
reasons = []
|
||||||
|
|
||||||
|
# MACD-Gründe
|
||||||
|
reasons.append(f"MACD-Signal: {macd_signal.value} (Konfidenz: {macd_details['confidence']}%)")
|
||||||
|
reasons.extend(macd_details.get('reasons', []))
|
||||||
|
|
||||||
|
# Sentiment-Gründe
|
||||||
|
reasons.append(
|
||||||
|
f"Markt-Sentiment: {sentiment.value} "
|
||||||
|
f"(Konfidenz: {sentiment_details['confidence']}%, "
|
||||||
|
f"Score: {sentiment_details['average_sentiment']:.3f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Zusätzliche Hinweise
|
||||||
|
if macd_signal in [MACDSignal.STRONG_BUY, MACDSignal.BUY] and \
|
||||||
|
sentiment in [SentimentScore.VERY_POSITIVE, SentimentScore.POSITIVE]:
|
||||||
|
reasons.append("✅ MACD und Sentiment stimmen überein → Starkes Signal")
|
||||||
|
elif macd_signal in [MACDSignal.STRONG_SELL, MACDSignal.SELL] and \
|
||||||
|
sentiment in [SentimentScore.VERY_NEGATIVE, SentimentScore.NEGATIVE]:
|
||||||
|
reasons.append("✅ MACD und Sentiment stimmen überein → Starkes Signal")
|
||||||
|
elif (macd_signal in [MACDSignal.STRONG_BUY, MACDSignal.BUY] and
|
||||||
|
sentiment in [SentimentScore.NEGATIVE, SentimentScore.VERY_NEGATIVE]) or \
|
||||||
|
(macd_signal in [MACDSignal.STRONG_SELL, MACDSignal.SELL] and
|
||||||
|
sentiment in [SentimentScore.POSITIVE, SentimentScore.VERY_POSITIVE]):
|
||||||
|
reasons.append("⚠️ MACD und Sentiment widersprechen sich → Vorsicht geboten")
|
||||||
|
confidence = max(30, confidence - 20) # Reduziere Konfidenz
|
||||||
|
|
||||||
|
# Erstelle Trading-Signal
|
||||||
|
signal = TradingSignal(
|
||||||
|
action=action,
|
||||||
|
confidence=confidence,
|
||||||
|
price=current_price,
|
||||||
|
timestamp=datetime.now(),
|
||||||
|
macd_signal=macd_signal,
|
||||||
|
sentiment=sentiment,
|
||||||
|
reasons=reasons,
|
||||||
|
technical_details=macd_details,
|
||||||
|
sentiment_details=sentiment_details
|
||||||
|
)
|
||||||
|
|
||||||
|
return signal
|
||||||
|
|
||||||
|
def get_recommendation_text(self, signal: TradingSignal) -> str:
|
||||||
|
"""
|
||||||
|
Generiert Empfehlungstext
|
||||||
|
|
||||||
|
Args:
|
||||||
|
signal: Trading-Signal
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatierter Empfehlungstext
|
||||||
|
"""
|
||||||
|
recommendation = str(signal)
|
||||||
|
recommendation += signal.format_reasons()
|
||||||
|
|
||||||
|
# Füge Handlungsempfehlung hinzu
|
||||||
|
recommendation += "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
||||||
|
recommendation += "💡 HANDLUNGSEMPFEHLUNG\n"
|
||||||
|
recommendation += "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
|
||||||
|
if signal.action == TradingAction.STRONG_BUY:
|
||||||
|
recommendation += " 🟢 STARKE KAUFGELEGENHEIT\n"
|
||||||
|
recommendation += " → Erwäge einen Einstieg oder Aufstockung der Position\n"
|
||||||
|
recommendation += " → Setze Stop-Loss ca. 5-7% unter Einstiegspreis\n"
|
||||||
|
elif signal.action == TradingAction.BUY:
|
||||||
|
recommendation += " 🟢 KAUFGELEGENHEIT\n"
|
||||||
|
recommendation += " → Erwäge einen Einstieg mit kleiner Position\n"
|
||||||
|
recommendation += " → Warte ggf. auf Bestätigung durch weitere Signale\n"
|
||||||
|
elif signal.action == TradingAction.HOLD:
|
||||||
|
recommendation += " 🟡 ABWARTEN\n"
|
||||||
|
recommendation += " → Keine klare Richtung erkennbar\n"
|
||||||
|
recommendation += " → Behalte den Markt im Auge für klarere Signale\n"
|
||||||
|
elif signal.action == TradingAction.SELL:
|
||||||
|
recommendation += " 🔴 VERKAUFSSIGNAL\n"
|
||||||
|
recommendation += " → Erwäge Teilverkauf oder Gewinnmitnahme\n"
|
||||||
|
recommendation += " → Ziehe Stop-Loss nach, um Gewinne zu sichern\n"
|
||||||
|
elif signal.action == TradingAction.STRONG_SELL:
|
||||||
|
recommendation += " 🔴 STARKES VERKAUFSSIGNAL\n"
|
||||||
|
recommendation += " → Erwäge Ausstieg aus Position\n"
|
||||||
|
recommendation += " → Sichere Gewinne oder begrenze Verluste\n"
|
||||||
|
|
||||||
|
recommendation += f"\n ⚠️ Risiko-Hinweis: Diese Analyse hat eine Konfidenz von {signal.confidence}%\n"
|
||||||
|
recommendation += " ⚠️ Keine Anlageberatung - Trading auf eigenes Risiko!\n"
|
||||||
|
|
||||||
|
recommendation += "\n╚══════════════════════════════════════════════════════════════════╝\n"
|
||||||
|
|
||||||
|
return recommendation
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test
|
||||||
|
from data_fetcher import BitcoinDataFetcher
|
||||||
|
|
||||||
|
print("=== Bitcoin Trading Signal Generator ===\n")
|
||||||
|
print("Lade Daten...\n")
|
||||||
|
|
||||||
|
# Hole Preisdaten
|
||||||
|
fetcher = BitcoinDataFetcher()
|
||||||
|
price_df = fetcher.get_historical_data(days=30)
|
||||||
|
current_price = fetcher.get_current_price()
|
||||||
|
|
||||||
|
if price_df.empty or not current_price:
|
||||||
|
print("Fehler beim Laden der Daten!")
|
||||||
|
else:
|
||||||
|
# Generiere Signal
|
||||||
|
generator = SignalGenerator()
|
||||||
|
signal = generator.generate_signal(price_df, current_price)
|
||||||
|
|
||||||
|
# Zeige Empfehlung
|
||||||
|
recommendation = generator.get_recommendation_text(signal)
|
||||||
|
print(recommendation)
|
||||||
Reference in New Issue
Block a user