Spaces:
No application file
No application file
| """ | |
| 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="<green>{time:YYYY-MM-DD HH:mm:ss:ms}</green> | " | |
| "<level>{level}</level> | " | |
| "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - " | |
| "<level>{message}</level>", | |
| 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 | |
| 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) | |