#!/usr/bin/env python3 """ 📰 NEWS FILTER - Economic Calendar Integration Blockiert Trading vor/nach High-Impact News Events FEATURES: - ForexFactory Calendar Scraping (kostenlos, keine API Key nötig) - High-Impact Events Filter - 30min Buffer vor/nach Event - USD, EUR, GBP News (relevant für Gold) - Cache für Performance VERWENDUNG: from news_filter 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 from bs4 import BeautifulSoup import json import os from typing import List, Dict, Optional class EconomicCalendar: """ Economic Calendar - ForexFactory Scraper """ def __init__(self, cache_minutes: int = 15): """ Initialize Economic Calendar Args: cache_minutes: Cache duration in minutes """ self.cache_file = "news_cache.json" self.cache_duration = timedelta(minutes=cache_minutes) self.last_fetch = None self.cached_events = [] def _get_forexfactory_calendar(self) -> List[Dict]: """ Scrape ForexFactory Calendar Returns: List of news events """ try: # ForexFactory calendar URL url = "https://www.forexfactory.com/calendar" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } response = requests.get(url, headers=headers, timeout=10) if response.status_code != 200: print(f"⚠️ ForexFactory returned status {response.status_code}") return [] soup = BeautifulSoup(response.content, 'html.parser') # Parse calendar events events = [] calendar_rows = soup.find_all('tr', class_='calendar__row') current_date = datetime.now().date() for row in calendar_rows: try: # Get time time_elem = row.find('td', class_='calendar__time') if not time_elem: continue time_text = time_elem.get_text(strip=True) if not time_text or time_text in ['All Day', 'Day']: continue # Get currency currency_elem = row.find('td', class_='calendar__currency') if not currency_elem: continue currency = currency_elem.get_text(strip=True) # Get impact impact_elem = row.find('td', class_='calendar__impact') if not impact_elem: continue impact_spans = impact_elem.find_all('span', class_='icon--ff-impact-gra') impact_level = len(impact_spans) # 1=low, 2=medium, 3=high # Get event name event_elem = row.find('td', class_='calendar__event') if not event_elem: continue event_name = event_elem.get_text(strip=True) # Only track USD, EUR, GBP (relevant for Gold) if currency not in ['USD', 'EUR', 'GBP']: continue # Only track high-impact events (level 3) if impact_level < 3: continue # Parse time try: event_time = datetime.strptime( f"{current_date} {time_text}", "%Y-%m-%d %I:%M%p" ) except: continue events.append({ 'time': event_time.isoformat(), 'currency': currency, 'event': event_name, 'impact': 'HIGH', 'impact_level': impact_level }) except Exception as e: # Skip malformed rows continue return events except Exception as e: print(f"❌ Error fetching ForexFactory 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...") events = self._get_forexfactory_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() -> EconomicCalendar: """Get or create global calendar instance""" global _calendar if _calendar is None: _calendar = EconomicCalendar() 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) # ========================================== # TESTING # ========================================== if __name__ == "__main__": print("="*70) print("📰 NEWS FILTER - TEST") print("="*70) print() # Test 1: Fetch calendar print("Test 1: Fetching calendar...") calendar = EconomicCalendar() 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)