Files
Place-Order-Trading-Bot/news_filter_v2.py
T

446 lines
13 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
📰 NEWS FILTER V2 - Economic Calendar (Finnhub API)
Blockiert Trading vor/nach High-Impact News Events
FREE API: Finnhub (60 calls/minute free tier)
Sign up: https://finnhub.io/register
FEATURES:
- Finnhub Economic Calendar API
- High-Impact Events Filter
- 30min Buffer vor/nach Event
- USD, EUR, GBP News (relevant für Gold)
- Cache für Performance
SETUP:
1. Sign up at https://finnhub.io/register
2. Get your free API key
3. Add to news_config.json:
{"finnhub_api_key": "YOUR_API_KEY_HERE"}
VERWENDUNG:
from news_filter_v2 import is_news_upcoming, get_upcoming_news
if is_news_upcoming(minutes_ahead=30):
print("⏸️ Trading SKIP: High-Impact News Event")
return
"""
import requests
from datetime import datetime, timedelta
import json
import os
from typing import List, Dict, Optional
class EconomicCalendarV2:
"""
Economic Calendar V2 - Finnhub API
"""
def __init__(self, api_key: Optional[str] = None, cache_minutes: int = 15):
"""
Initialize Economic Calendar
Args:
api_key: Finnhub API key (or load from config)
cache_minutes: Cache duration in minutes
"""
# Load API key
if api_key:
self.api_key = api_key
else:
self.api_key = self._load_api_key()
self.cache_file = "news_cache.json"
self.cache_duration = timedelta(minutes=cache_minutes)
self.last_fetch = None
self.cached_events = []
# High-impact event keywords
self.high_impact_keywords = [
'NFP', 'Non-Farm', 'Payrolls', 'Employment',
'CPI', 'Inflation', 'Consumer Price',
'GDP', 'Gross Domestic Product',
'FOMC', 'Fed', 'Federal Reserve', 'Interest Rate',
'PMI', 'Manufacturing',
'Retail Sales',
'Unemployment',
'ECB', 'European Central Bank',
'BOE', 'Bank of England'
]
def _load_api_key(self) -> str:
"""Load API key from config file"""
config_file = "news_config.json"
if os.path.exists(config_file):
try:
with open(config_file, 'r') as f:
config = json.load(f)
return config.get('finnhub_api_key', '')
except:
pass
# Default demo key (limited, should replace with own)
return "demo" # Replace with your own key!
def _is_high_impact(self, event_name: str) -> bool:
"""Check if event is high impact based on keywords"""
event_lower = event_name.lower()
for keyword in self.high_impact_keywords:
if keyword.lower() in event_lower:
return True
return False
def _get_finnhub_calendar(self) -> List[Dict]:
"""
Fetch economic calendar from Finnhub
Returns:
List of news events
"""
try:
# Get today's date range
today = datetime.now()
from_date = (today - timedelta(days=1)).strftime('%Y-%m-%d')
to_date = (today + timedelta(days=7)).strftime('%Y-%m-%d')
url = "https://finnhub.io/api/v1/calendar/economic"
params = {
'token': self.api_key,
'from': from_date,
'to': to_date
}
response = requests.get(url, params=params, timeout=10)
if response.status_code != 200:
print(f"⚠️ Finnhub returned status {response.status_code}")
return []
data = response.json()
if 'economicCalendar' not in data:
print("⚠️ No calendar data in response")
return []
# Parse events
events = []
for item in data['economicCalendar']:
try:
event_name = item.get('event', '')
country = item.get('country', '')
event_time_str = item.get('time', '')
# Only USD, EUR, GBP events
if country not in ['US', 'EU', 'GB', 'UK']:
continue
# Filter by high-impact keywords
if not self._is_high_impact(event_name):
continue
# Parse time
try:
event_time = datetime.fromisoformat(event_time_str.replace('Z', '+00:00'))
except:
continue
# Map country to currency
currency_map = {'US': 'USD', 'EU': 'EUR', 'GB': 'GBP', 'UK': 'GBP'}
currency = currency_map.get(country, country)
events.append({
'time': event_time.isoformat(),
'currency': currency,
'event': event_name,
'impact': 'HIGH',
'country': country,
'actual': item.get('actual'),
'estimate': item.get('estimate'),
'previous': item.get('previous')
})
except Exception as e:
continue
return events
except Exception as e:
print(f"❌ Error fetching Finnhub calendar: {e}")
return []
def _load_cache(self) -> bool:
"""Load events from cache"""
if not os.path.exists(self.cache_file):
return False
try:
with open(self.cache_file, 'r') as f:
data = json.load(f)
cache_time = datetime.fromisoformat(data['timestamp'])
# Check if cache is still valid
if datetime.now() - cache_time < self.cache_duration:
self.cached_events = data['events']
self.last_fetch = cache_time
return True
except Exception as e:
print(f"⚠️ Cache load error: {e}")
return False
def _save_cache(self, events: List[Dict]):
"""Save events to cache"""
try:
data = {
'timestamp': datetime.now().isoformat(),
'events': events
}
with open(self.cache_file, 'w') as f:
json.dump(data, f, indent=2)
except Exception as e:
print(f"⚠️ Cache save error: {e}")
def get_news_events(self, force_refresh: bool = False) -> List[Dict]:
"""
Get news events (cached or fresh)
Args:
force_refresh: Force fetch from API
Returns:
List of news events
"""
# Try cache first
if not force_refresh and self._load_cache():
return self.cached_events
# Fetch fresh data
print("📰 Fetching economic calendar from Finnhub...")
events = self._get_finnhub_calendar()
if events:
self.cached_events = events
self.last_fetch = datetime.now()
self._save_cache(events)
print(f"✅ Fetched {len(events)} high-impact events")
else:
print("⚠️ No events fetched, using cache if available")
self._load_cache()
return self.cached_events
def get_upcoming_news(self, minutes_ahead: int = 60, minutes_after: int = 30) -> List[Dict]:
"""
Get upcoming news events within time window
Args:
minutes_ahead: Look ahead this many minutes
minutes_after: Consider events from this many minutes ago
Returns:
List of upcoming events
"""
events = self.get_news_events()
now = datetime.now()
start_window = now - timedelta(minutes=minutes_after)
end_window = now + timedelta(minutes=minutes_ahead)
upcoming = []
for event in events:
try:
event_time = datetime.fromisoformat(event['time'])
if start_window <= event_time <= end_window:
# Add time until event
delta = event_time - now
minutes_until = int(delta.total_seconds() / 60)
event_copy = event.copy()
event_copy['minutes_until'] = minutes_until
upcoming.append(event_copy)
except Exception as e:
continue
# Sort by time
upcoming.sort(key=lambda x: x['minutes_until'])
return upcoming
def is_news_upcoming(self, minutes_ahead: int = 30, minutes_after: int = 30) -> tuple:
"""
Check if high-impact news is upcoming
Args:
minutes_ahead: Block trading if news within this many minutes ahead
minutes_after: Block trading if news was this many minutes ago
Returns:
(is_upcoming: bool, events: List[Dict])
"""
upcoming = self.get_upcoming_news(minutes_ahead, minutes_after)
return len(upcoming) > 0, upcoming
# ==========================================
# CONVENIENCE FUNCTIONS
# ==========================================
# Global calendar instance
_calendar = None
def get_calendar() -> EconomicCalendarV2:
"""Get or create global calendar instance"""
global _calendar
if _calendar is None:
_calendar = EconomicCalendarV2()
return _calendar
def is_news_upcoming(minutes_ahead: int = 30, minutes_after: int = 30) -> bool:
"""
Quick check if news is upcoming
Args:
minutes_ahead: Block if news within this many minutes
minutes_after: Block if news was this many minutes ago
Returns:
True if news is upcoming, False otherwise
"""
calendar = get_calendar()
is_upcoming, events = calendar.is_news_upcoming(minutes_ahead, minutes_after)
if is_upcoming:
print("⏸️ HIGH-IMPACT NEWS DETECTED:")
for event in events:
minutes = event['minutes_until']
time_str = f"in {minutes}min" if minutes > 0 else f"{abs(minutes)}min ago"
print(f" • {event['currency']} {event['event']} - {time_str}")
return is_upcoming
def get_upcoming_news(minutes_ahead: int = 60) -> List[Dict]:
"""
Get list of upcoming news events
Args:
minutes_ahead: Look ahead this many minutes
Returns:
List of news events
"""
calendar = get_calendar()
return calendar.get_upcoming_news(minutes_ahead)
def force_refresh_calendar():
"""Force refresh calendar data"""
calendar = get_calendar()
calendar.get_news_events(force_refresh=True)
# ==========================================
# SETUP HELPER
# ==========================================
def create_config_template():
"""Create news_config.json template"""
config = {
"finnhub_api_key": "YOUR_API_KEY_HERE",
"instructions": "Get your free API key at https://finnhub.io/register"
}
with open('news_config.json', 'w') as f:
json.dump(config, f, indent=2)
print("✅ Created news_config.json template")
print("📝 Edit the file and add your Finnhub API key")
print(" Sign up at: https://finnhub.io/register")
# ==========================================
# TESTING
# ==========================================
if __name__ == "__main__":
print("="*70)
print("📰 NEWS FILTER V2 - TEST (Finnhub API)")
print("="*70)
print()
# Check if config exists
if not os.path.exists('news_config.json'):
print("⚠️ news_config.json not found")
print("Creating template...")
create_config_template()
print()
print("Please edit news_config.json and add your API key, then run again")
exit()
# Test 1: Fetch calendar
print("Test 1: Fetching calendar...")
calendar = EconomicCalendarV2()
events = calendar.get_news_events(force_refresh=True)
print(f"✅ Found {len(events)} high-impact events")
print()
# Test 2: Show upcoming events
print("Test 2: Upcoming events (next 4 hours)...")
upcoming = calendar.get_upcoming_news(minutes_ahead=240)
if upcoming:
print(f"Found {len(upcoming)} upcoming events:")
for event in upcoming:
minutes = event['minutes_until']
time_str = f"in {minutes}min" if minutes > 0 else f"{abs(minutes)}min ago"
print(f" • {event['currency']} {event['event']} - {time_str}")
else:
print("No upcoming high-impact events in next 4 hours")
print()
# Test 3: Check if trading should be blocked
print("Test 3: Should trading be blocked? (30min window)")
is_upcoming, blocking_events = calendar.is_news_upcoming(
minutes_ahead=30,
minutes_after=30
)
if is_upcoming:
print("❌ YES - Trading should be BLOCKED")
print("Blocking events:")
for event in blocking_events:
minutes = event['minutes_until']
time_str = f"in {minutes}min" if minutes > 0 else f"{abs(minutes)}min ago"
print(f" • {event['currency']} {event['event']} - {time_str}")
else:
print("✅ NO - Trading is safe")
print()
print("="*70)
print("✅ TEST COMPLETE")
print("="*70)