#!/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)