""" Complete startup script for AFML Cache + MQL5 Integration Handles proper initialization order and connection verification. """ import sys import time from datetime import datetime from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] if __package__ in (None, "") and str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) import numpy as np import pandas as pd from loguru import logger try: from . import cacheable, initialize_cache_system from .mql5_bridge import MQL5Bridge, SignalPacket except ImportError: from afml.cache import cacheable, initialize_cache_system from afml.cache.mql5_bridge import MQL5Bridge, SignalPacket # Configure logging logger.remove() logger.add( sys.stderr, level="INFO", format="{time:YYYY-MM-DD HH:mm:ss:ms} | " "{level} | " "{name}:{function}:{line} - " "{message}", colorize=True, enqueue=True, ) logger.add( Path("logs", "mql5_bridge_{time:YYYY-MM-DD}.log"), rotation="1 day", retention="7 days", level="DEBUG", enqueue=True, ) def wait_for_connection(bridge: MQL5Bridge, timeout: int = 60) -> bool: """ Wait for MQL5 to connect with timeout. Args: bridge: MQL5Bridge instance timeout: Maximum seconds to wait Returns: True if connected, False if timeout """ logger.info(f"Waiting for MQL5 connection (timeout: {timeout}s)...") start_time = time.time() last_message = time.time() while time.time() - start_time < timeout: if bridge.client_socket is not None: logger.success("[OK] MQL5 client connected!") return True # Print waiting message every 5 seconds if time.time() - last_message >= 5: elapsed = int(time.time() - start_time) logger.info(f"Still waiting... ({elapsed}s elapsed)") last_message = time.time() time.sleep(0.5) logger.error(f"[ERROR] Connection timeout after {timeout}s") return False def verify_server_listening(host: str, port: int) -> bool: """ Verify that the Python server is actually listening on the port. Args: host: Server host port: Server port Returns: True if server is listening """ import socket try: # Try to connect to ourselves to verify test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_socket.settimeout(2) # This should succeed if server is listening result = test_socket.connect_ex((host, port)) test_socket.close() if result == 0: logger.success(f"[OK] Server is listening on {host}:{port}") return True else: logger.error(f"[ERROR] Server not listening on {host}:{port} (error: {result})") return False except Exception as e: logger.error(f"[ERROR] Failed to verify server: {e}") return False def check_port_available(port: int) -> bool: """ Check if port is available before starting server. Args: port: Port to check Returns: True if available, False if in use """ import socket try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", port)) s.close() logger.success(f"[OK] Port {port} is available") return True except OSError as e: logger.error(f"[ERROR] Port {port} is in use: {e}") return False @cacheable() def generate_test_features(data: pd.DataFrame) -> pd.DataFrame: """Generate test features (cached).""" features = data.copy() features["sma_20"] = data["close"].rolling(20).mean() features["rsi"] = 50 + np.random.randn(len(data)) * 10 # Simplified return features.dropna() def generate_test_signal(data: pd.DataFrame) -> SignalPacket: """Generate a test trading signal.""" latest_price = data["close"].iloc[-1] return SignalPacket( timestamp=datetime.now().isoformat(), symbol="EURUSD", signal_type="BUY", entry_price=latest_price, stop_loss=latest_price - 0.0050, take_profit=latest_price + 0.0100, position_size=0.01, confidence=0.75, strategy_name="test_strategy", metadata={"test": True, "generated_at": datetime.now().isoformat()}, ) def run_startup_checks(port: int = 5056) -> bool: """ Run all startup checks before starting bridge. Returns: True if all checks pass """ logger.info("Running startup checks...") # Check 1: Cache system try: initialize_cache_system() logger.success("[OK] Cache system initialized") except Exception as e: logger.error(f"[ERROR] Cache initialization failed: {e}") return False # Check 2: Port availability if not check_port_available(port): logger.error(f"Port {port} is not available") logger.info( f"Try: netstat -ano | findstr :{port} (Windows)" ) return False # Check 3: Test cache functionality try: test_data = pd.DataFrame({"close": np.random.randn(100) + 1.1000}) generate_test_features(test_data) logger.success("[OK] Cache functionality verified") except Exception as e: logger.error(f"[ERROR] Cache test failed: {e}") return False logger.success("[OK] All startup checks passed") return True def print_startup_instructions(host: str = "127.0.0.1", port: int = 5056): """Print clear instructions for user.""" print("\n" + "=" * 70) print("MQL5 CONNECTION INSTRUCTIONS") print("=" * 70) print("\nFollow these steps IN ORDER:\n") print("1. [OK] Python server is now running (you're seeing this message)") print("2. Open MetaTrader 5") print("3. Open any chart (e.g., EURUSD, M5)") print("4. Drag 'PythonBridgeEA' from Navigator -> Expert Advisors") print("5. In the EA settings, verify:") print(f" - PythonHost: {host}") print(f" - PythonPort: {port}") print(" - EnableTrading: false (for testing)") print("6. [OK] Click OK") print("7. Enable AutoTrading (Ctrl+E or click AutoTrading button)") print("8. Watch the 'Experts' tab for connection message") print("\n" + "=" * 70) print("Waiting for MQL5 to connect...") print("=" * 70 + "\n") def main_live_mode( host: str = "127.0.0.1", port: int = 5056, send_test_signal: bool = False ): """ Main function for live trading mode with proper connection handling. """ logger.info("=" * 70) logger.info("AFML Cache + MQL5 Integration - LIVE MODE") logger.info("=" * 70) # Step 1: Run startup checks if not run_startup_checks(port): logger.error("Startup checks failed. Exiting.") return # Step 2: Create and start bridge logger.info("\nStarting MQL5 bridge...") bridge = MQL5Bridge(host=host, port=port, mode="live") try: bridge.start_server() time.sleep(1) # Give server time to bind # Step 3: Server bind succeeded. Do not self-connect here because the # bridge accepts only one active MT5-style client at a time. logger.success(f"[OK] Server is listening on {host}:{port}") # Step 4: Print instructions print_startup_instructions(host, port) # Step 5: Wait for connection if not wait_for_connection(bridge, timeout=3600): logger.error("\n[ERROR] MQL5 did not connect within timeout period") logger.info("\nTroubleshooting:") logger.info("1. Is MetaTrader 5 running?") logger.info("2. Is the EA attached to a chart?") logger.info("3. Is AutoTrading enabled? (Ctrl+E)") logger.info("4. Check the 'Experts' tab for error messages") logger.info("5. Check Windows Firewall settings") return # Step 6: Connection established logger.info("\n" + "=" * 70) logger.info("CONNECTION ESTABLISHED") logger.info("=" * 70) time.sleep(2) # Let MQL5 settle if send_test_signal: # Generate and send an explicit opt-in smoke signal. test_data = pd.DataFrame( { "close": 1.1000 + np.random.randn(100) * 0.0010, "high": 1.1000 + np.random.randn(100) * 0.0010 + 0.0005, "low": 1.1000 + np.random.randn(100) * 0.0010 - 0.0005, } ) logger.info("Sending test signal...") test_signal = generate_test_signal(test_data) success = bridge.send_signal(test_signal) if success: logger.success("[OK] Test signal sent successfully!") else: logger.warning("[WARN] Test signal queued (will send when MQL5 requests)") else: logger.info("Test signal disabled. Use --send-test-signal to send one.") time.sleep(2) # Step 7: Print stats stats = bridge.get_performance_stats() logger.info("\n" + "=" * 70) logger.info("BRIDGE STATUS") logger.info("=" * 70) logger.info(f"Connected: {stats['connected']}") logger.info(f"Signals Sent: {stats['signals_sent']}") logger.info(f"Pending Signals: {stats['pending_signals']}") logger.info(f"Uptime: {stats['uptime_seconds']:.0f}s") logger.info("=" * 70) # Step 8: Keep running logger.info("\n[OK] Bridge is running. Press Ctrl+C to stop.\n") # Main loop - keep bridge alive and send periodic updates update_counter = 0 try: while True: time.sleep(10) update_counter += 1 # Print status every 60 seconds if update_counter % 6 == 0: stats = bridge.get_performance_stats() logger.info( f"Status: Connected={stats['connected']}, " f"Signals={stats['signals_sent']}, " f"Executed={stats['signals_executed']}" ) # Check if we lost connection if not bridge.client_socket: logger.warning( "[WARN] Lost connection to MQL5. Waiting for reconnect..." ) if wait_for_connection(bridge, timeout=30): logger.success("[OK] Reconnected to MQL5") except KeyboardInterrupt: logger.info("\n\nShutting down gracefully...") except Exception as e: logger.error(f"Error in main loop: {e}") import traceback logger.error(traceback.format_exc()) finally: bridge.stop() logger.info("Bridge stopped. Goodbye!") def main_test_mode(host: str = "127.0.0.1", port: int = 5056): """ Test mode - just verify everything works without waiting for MQL5. """ logger.info("=" * 70) logger.info("QUICK TEST MODE") logger.info("=" * 70) # Run checks if not run_startup_checks(port): return # Start bridge logger.info("\nStarting bridge...") bridge = MQL5Bridge(host=host, port=port, mode="live") bridge.start_server() time.sleep(1) # Server bind succeeded. Avoid a dummy self-connection that looks like MT5. logger.success(f"[OK] Server is listening on {host}:{port}") # Try sending a test signal (will be queued) logger.info("\nTesting signal generation...") test_data = pd.DataFrame({"close": 1.1000 + np.random.randn(100) * 0.0010}) test_signal = generate_test_signal(test_data) bridge.send_signal(test_signal) stats = bridge.get_performance_stats() logger.info(f"\nBridge Stats: {stats}") logger.success("\n[OK] Test completed successfully!") logger.info("Run with --live flag to wait for MQL5 connection") bridge.stop() if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="AFML MQL5 Bridge") parser.add_argument( "--mode", choices=["live", "test"], default="test", help="Run mode: 'live' waits for MQL5, 'test' just verifies setup", ) parser.add_argument("--host", default="127.0.0.1", help="Bridge host") parser.add_argument("--port", type=int, default=5056, help="Bridge port") parser.add_argument( "--send-test-signal", action="store_true", help="Send the built-in EURUSD smoke signal after MQL5 connects.", ) args = parser.parse_args() if args.mode == "live": main_live_mode( host=args.host, port=args.port, send_test_signal=args.send_test_signal ) else: main_test_mode(host=args.host, port=args.port)