fix: equity_curve_trading, infrastructure_patch + news filter cleanup
equity_curve_trading.py: - mt→mt5 alias in _get_current_equity() - safe-fail: return False (block trade) when equity unavailable - UTC timestamps via timezone.utc in update_equity() - add_initial_equity(): unique timestamps (staggered by minute) instead of identical infrastructure_patch.py: - Add logging module, replace all print() with logger calls - Fix guard: self.db/self.telegram instead of enable_database/enable_telegram - Fix UTC bug in extract_trade_data_from_mt5() (fromtimestamp with tz=utc) - Remove direct self.db.cursor.execute() in log_trade_exit() — use get_open_trades() - Read risk_pct from SESSION_WHITELIST_CONFIG instead of hardcoding 0.01 news_filter.py / news_filter_v2.py: - Remove both inactive variants (ForexFactory scraper + Finnhub API) - news_filter_simple.py + news_filter_integration.py remain as active implementation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ VERWENDUNG:
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone, timedelta
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -111,7 +111,8 @@ class EquityCurveManager:
|
|||||||
# Aktuelle Equity holen
|
# Aktuelle Equity holen
|
||||||
current_equity = self._get_current_equity(mt5_account_info)
|
current_equity = self._get_current_equity(mt5_account_info)
|
||||||
if current_equity is None:
|
if current_equity is None:
|
||||||
return True, "Could not get equity, allowing trade", 1.0
|
logger.error("Could not get equity from MT5 — blocking trade as safe default")
|
||||||
|
return False, "Equity unavailable — trade blocked for safety", 0.0
|
||||||
|
|
||||||
# MA berechnen
|
# MA berechnen
|
||||||
ma_equity = self._calculate_ma()
|
ma_equity = self._calculate_ma()
|
||||||
@@ -157,7 +158,7 @@ class EquityCurveManager:
|
|||||||
return
|
return
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
"timestamp": datetime.now().isoformat(),
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||||
"equity": current_equity,
|
"equity": current_equity,
|
||||||
"trade_count": len(self.equity_history) + 1
|
"trade_count": len(self.equity_history) + 1
|
||||||
}
|
}
|
||||||
@@ -255,8 +256,8 @@ class EquityCurveManager:
|
|||||||
return mt5_account_info.equity
|
return mt5_account_info.equity
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import MetaTrader5 as mt
|
import MetaTrader5 as mt5
|
||||||
account = mt.account_info()
|
account = mt5.account_info()
|
||||||
if account:
|
if account:
|
||||||
return account.equity
|
return account.equity
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -306,9 +307,11 @@ class EquityCurveManager:
|
|||||||
|
|
||||||
Nützlich wenn du mit bestehendem Konto startest
|
Nützlich wenn du mit bestehendem Konto startest
|
||||||
"""
|
"""
|
||||||
|
base_time = datetime.now(timezone.utc)
|
||||||
for i in range(self.min_trades):
|
for i in range(self.min_trades):
|
||||||
|
ts = (base_time - timedelta(minutes=self.min_trades - i)).isoformat()
|
||||||
self.equity_history.append({
|
self.equity_history.append({
|
||||||
"timestamp": datetime.now().isoformat(),
|
"timestamp": ts,
|
||||||
"equity": equity,
|
"equity": equity,
|
||||||
"trade_count": i + 1,
|
"trade_count": i + 1,
|
||||||
"note": "Initial warmup entry"
|
"note": "Initial warmup entry"
|
||||||
|
|||||||
+46
-39
@@ -16,10 +16,13 @@ USAGE:
|
|||||||
|
|
||||||
from trading_database import TradingDatabase
|
from trading_database import TradingDatabase
|
||||||
from telegram_notifier import TelegramNotifier, load_telegram_config
|
from telegram_notifier import TelegramNotifier, load_telegram_config
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from typing import Dict, Optional, Callable
|
from typing import Dict, Optional, Callable
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TradingInfrastructure:
|
class TradingInfrastructure:
|
||||||
@@ -50,9 +53,9 @@ class TradingInfrastructure:
|
|||||||
if self.enable_database:
|
if self.enable_database:
|
||||||
try:
|
try:
|
||||||
self.db = TradingDatabase(db_path)
|
self.db = TradingDatabase(db_path)
|
||||||
print(f"✅ Database initialized: {db_path}")
|
logger.info(f"Database initialized: {db_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Database initialization failed: {e}")
|
logger.error(f"Database initialization failed: {e}")
|
||||||
self.enable_database = False
|
self.enable_database = False
|
||||||
self.db = None
|
self.db = None
|
||||||
else:
|
else:
|
||||||
@@ -69,13 +72,13 @@ class TradingInfrastructure:
|
|||||||
bot_token=telegram_config['bot_token'],
|
bot_token=telegram_config['bot_token'],
|
||||||
chat_id=telegram_config['chat_id']
|
chat_id=telegram_config['chat_id']
|
||||||
)
|
)
|
||||||
print("✅ Telegram notifications enabled")
|
logger.info("Telegram notifications enabled")
|
||||||
else:
|
else:
|
||||||
print("⚠️ No Telegram config found, notifications disabled")
|
logger.warning("No Telegram config found, notifications disabled")
|
||||||
self.enable_telegram = False
|
self.enable_telegram = False
|
||||||
self.telegram = None
|
self.telegram = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Telegram initialization failed: {e}")
|
logger.error(f"Telegram initialization failed: {e}")
|
||||||
self.enable_telegram = False
|
self.enable_telegram = False
|
||||||
self.telegram = None
|
self.telegram = None
|
||||||
else:
|
else:
|
||||||
@@ -94,18 +97,17 @@ class TradingInfrastructure:
|
|||||||
trade_data: Dictionary with trade information
|
trade_data: Dictionary with trade information
|
||||||
"""
|
"""
|
||||||
# Log to database
|
# Log to database
|
||||||
if self.enable_database and self.db:
|
if self.db:
|
||||||
try:
|
try:
|
||||||
self.db.log_trade_entry(trade_data)
|
self.db.log_trade_entry(trade_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Database logging failed: {e}")
|
logger.error(f"Database logging failed: {e}")
|
||||||
|
|
||||||
# Send Telegram notification
|
if self.telegram:
|
||||||
if self.enable_telegram and self.telegram:
|
|
||||||
try:
|
try:
|
||||||
self.telegram.notify_trade_entry(trade_data)
|
self.telegram.notify_trade_entry(trade_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Telegram notification failed: {e}")
|
logger.error(f"Telegram notification failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
def log_trade_exit(self, ticket: int, exit_data: Dict):
|
def log_trade_exit(self, ticket: int, exit_data: Dict):
|
||||||
@@ -117,32 +119,28 @@ class TradingInfrastructure:
|
|||||||
exit_data: Dictionary with exit information
|
exit_data: Dictionary with exit information
|
||||||
"""
|
"""
|
||||||
# Update database
|
# Update database
|
||||||
if self.enable_database and self.db:
|
if self.db:
|
||||||
try:
|
try:
|
||||||
self.db.update_trade_exit(ticket, exit_data)
|
self.db.update_trade_exit(ticket, exit_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Database update failed: {e}")
|
logger.error(f"Database update failed: {e}")
|
||||||
|
|
||||||
# Send Telegram notification
|
# Send Telegram notification
|
||||||
if self.enable_telegram and self.telegram:
|
if self.telegram:
|
||||||
try:
|
try:
|
||||||
# Get full trade data from database
|
|
||||||
if self.db:
|
if self.db:
|
||||||
trades = self.db.cursor.execute(
|
open_trades = self.db.get_open_trades()
|
||||||
"SELECT * FROM trades WHERE ticket = ?",
|
trade_row = next((t for t in open_trades if t.get('ticket') == ticket), None)
|
||||||
(ticket,)
|
if trade_row:
|
||||||
).fetchone()
|
trade_dict = dict(trade_row)
|
||||||
|
|
||||||
if trades:
|
|
||||||
trade_dict = dict(trades)
|
|
||||||
trade_dict.update(exit_data)
|
trade_dict.update(exit_data)
|
||||||
self.telegram.notify_trade_exit(trade_dict)
|
self.telegram.notify_trade_exit(trade_dict)
|
||||||
|
else:
|
||||||
|
self.telegram.notify_trade_exit(exit_data)
|
||||||
else:
|
else:
|
||||||
# Fallback if no database
|
|
||||||
self.telegram.notify_trade_exit(exit_data)
|
self.telegram.notify_trade_exit(exit_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Telegram notification failed: {e}")
|
logger.error(f"Telegram notification failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -201,6 +199,15 @@ class TradingInfrastructure:
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary with trade data
|
Dictionary with trade data
|
||||||
"""
|
"""
|
||||||
|
entry_time = datetime.fromtimestamp(position.time, tz=timezone.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
|
# risk_pct: try to read from SESSION_WHITELIST_CONFIG, fall back to 0.01
|
||||||
|
try:
|
||||||
|
from session_filter_patch import SESSION_WHITELIST_CONFIG
|
||||||
|
risk_pct = SESSION_WHITELIST_CONFIG.get('max_risk_per_trade', 0.01)
|
||||||
|
except Exception:
|
||||||
|
risk_pct = 0.01
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'ticket': position.ticket,
|
'ticket': position.ticket,
|
||||||
'position_id': position.identifier,
|
'position_id': position.identifier,
|
||||||
@@ -211,11 +218,11 @@ class TradingInfrastructure:
|
|||||||
'entry_price': position.price_open,
|
'entry_price': position.price_open,
|
||||||
'sl_price': position.sl,
|
'sl_price': position.sl,
|
||||||
'tp_price': position.tp,
|
'tp_price': position.tp,
|
||||||
'entry_time': datetime.fromtimestamp(position.time).strftime('%Y-%m-%d %H:%M:%S'),
|
'entry_time': entry_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
'session': session,
|
'session': session,
|
||||||
'confidence': confidence,
|
'confidence': confidence,
|
||||||
'quality': quality,
|
'quality': quality,
|
||||||
'risk_pct': 0.01, # From config
|
'risk_pct': risk_pct,
|
||||||
'status': 'open'
|
'status': 'open'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,11 +238,11 @@ class TradingInfrastructure:
|
|||||||
Args:
|
Args:
|
||||||
config: Bot configuration
|
config: Bot configuration
|
||||||
"""
|
"""
|
||||||
if self.enable_telegram and self.telegram:
|
if self.telegram:
|
||||||
try:
|
try:
|
||||||
self.telegram.send_bot_started(config)
|
self.telegram.send_bot_started(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Telegram notification failed: {e}")
|
logger.error(f"Telegram notification failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
def send_bot_stopped(self, reason: str = "Manual stop"):
|
def send_bot_stopped(self, reason: str = "Manual stop"):
|
||||||
@@ -245,28 +252,28 @@ class TradingInfrastructure:
|
|||||||
Args:
|
Args:
|
||||||
reason: Stop reason
|
reason: Stop reason
|
||||||
"""
|
"""
|
||||||
if self.enable_telegram and self.telegram:
|
if self.telegram:
|
||||||
try:
|
try:
|
||||||
self.telegram.send_bot_stopped(reason)
|
self.telegram.send_bot_stopped(reason)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Telegram notification failed: {e}")
|
logger.error(f"Telegram notification failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
def send_daily_report(self):
|
def send_daily_report(self):
|
||||||
"""Send daily performance report"""
|
"""Send daily performance report"""
|
||||||
if not (self.enable_database and self.enable_telegram):
|
if not (self.db and self.telegram):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stats = self.db.get_daily_summary()
|
stats = self.db.get_daily_summary()
|
||||||
self.telegram.send_daily_report(stats)
|
self.telegram.send_daily_report(stats)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Daily report failed: {e}")
|
logger.error(f"Daily report failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
def send_weekly_report(self):
|
def send_weekly_report(self):
|
||||||
"""Send weekly performance report"""
|
"""Send weekly performance report"""
|
||||||
if not (self.enable_database and self.enable_telegram):
|
if not (self.db and self.telegram):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -274,7 +281,7 @@ class TradingInfrastructure:
|
|||||||
session_perf = self.db.get_session_performance(days=7)
|
session_perf = self.db.get_session_performance(days=7)
|
||||||
self.telegram.send_weekly_report(stats, session_perf)
|
self.telegram.send_weekly_report(stats, session_perf)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Weekly report failed: {e}")
|
logger.error(f"Weekly report failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
def log_bot_status(self, status: str, config: Dict = None, error_message: str = None):
|
def log_bot_status(self, status: str, config: Dict = None, error_message: str = None):
|
||||||
@@ -286,7 +293,7 @@ class TradingInfrastructure:
|
|||||||
config: Optional bot configuration
|
config: Optional bot configuration
|
||||||
error_message: Optional error message
|
error_message: Optional error message
|
||||||
"""
|
"""
|
||||||
if not (self.enable_database and self.db):
|
if not self.db:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -301,7 +308,7 @@ class TradingInfrastructure:
|
|||||||
self.db.log_bot_status(status_data)
|
self.db.log_bot_status(status_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Status logging failed: {e}")
|
logger.error(f"Status logging failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -349,7 +356,7 @@ class TradingInfrastructure:
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary with statistics
|
Dictionary with statistics
|
||||||
"""
|
"""
|
||||||
if not (self.enable_database and self.db):
|
if not self.db:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -361,7 +368,7 @@ class TradingInfrastructure:
|
|||||||
'sessions': session_perf
|
'sessions': session_perf
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Performance summary failed: {e}")
|
logger.error(f"Performance summary failed: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
-378
@@ -1,378 +0,0 @@
|
|||||||
#!/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)
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
#!/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)
|
|
||||||
Reference in New Issue
Block a user